Overview

FetchClient holds a base FetchRequest (host, default headers, …) merged under every call, plus its own interceptor chain. Module helpers (Get, Post, …) wrap a shared default instance.

In apps — especially SSR — prefer useUtils().fetchClient and useUtils().interceptBefore / interceptAfter / interceptError. Those interceptors live on the request-scoped FetchUtils, not a process-global singleton.

Action factories (getAction, postAction, …) return asyncActions bound to the client — call with a params object for path/query substitution.

Client vs helpers

API
Scope
Prefer when
Get / Post / … Default singleton Scripts & one-offs — see Fetch & HTTP.
new FetchClient({ host }) Module / service Shared base URL + per-instance interceptors.
useUtils().fetchClient App / SSR Scoped interceptors isolated per MatesUtils.

FetchClient with shared host

One client, two GETs against JSONPlaceholder. Path params via params: { id }.

import { html, atom, FetchClient, renderApp } from 'mates';

const api = new FetchClient({
host: 'https://jsonplaceholder.typicode.com',
});

const App = () => {
const title = atom('');
const busy = atom(false);
const err = atom('');

const load = async (id) => {
busy.set(true); err.set(''); title.set('');
try {
const todo = await api.Get({ url: '/todos/:id', params: { id } });
title.set(todo.title);
} catch (e) {
err.set(e.message || String(e));
} finally {
busy.set(false);
}
};

return () => html`
<div class="m-col m-gap">
<div class="m-flex m-items-center m-justify-center m-gap-sm">
<button class="btn-primary" ?disabled=${busy()} @click=${() => load(1)}>Todo 1</button>
<button ?disabled=${busy()} @click=${() => load(2)}>Todo 2</button>
</div>
${busy() ? html`<span class="tag">loading…</span>` : ''}
${err() ? html`<p>${err()}</p>` : ''}
${title() ? html`<p class="muted">${title()}</p>` : ''}
</div>
`;
};
renderApp(App, document.getElementById('app'));

useUtils().fetchClient + interceptBefore

Scoped client from renderApp’s MatesUtils. interceptBefore logs each URL before the request.

import { html, atom, useUtils, renderApp } from 'mates';

const App = () => {
const { fetchClient, interceptBefore } = useUtils();
const log = atom([]);
const title = atom('');

interceptBefore((url, options) => {
log.update((rows) => { rows.push('before → ' + url); });
return { url, options };
});

const load = async () => {
title.set('');
const todo = await fetchClient.Get({
url: 'https://jsonplaceholder.typicode.com/todos/1',
});
title.set(todo.title);
};

return () => html`
<div class="m-col m-gap">
<button class="btn-primary" @click=${load}>Fetch via useUtils().fetchClient</button>
${title() ? html`<p class="muted">${title()}</p>` : ''}
<div class="card card-body">
<span class="overline">Interceptor log</span>
${log().map((line) => html`<div class="mono">${line}</div>`)}
</div>
</div>
`;
};
renderApp(App, document.getElementById('app'));