@corndev/query-react
v1.0.0-beta.4
Published
React bindings for @corndev/query — useQuery and useMutation
Maintainers
Readme
@corndev/query-react
React bindings for @corndev/query.
~1.3 KB min+gzip.
npm install @corndev/query @corndev/query-reactreact >= 18 is a peer dependency (the hooks are built on useSyncExternalStore).
Usage
import { createQueryClient } from '@corndev/query';
import { QueryClientProvider, useQuery, useMutation, useQueryClient } from '@corndev/query-react';
const client = createQueryClient({ defaultOptions: { staleTime: 30_000 } });
export default function App() {
return (
<QueryClientProvider client={client}>
<Todos />
</QueryClientProvider>
);
}
function Todos() {
const { data, status, isFetching, refetch } = useQuery({
queryKey: ['todos'],
queryFn: ({ signal }) => fetch('/api/todos', { signal }).then(r => r.json()),
});
const queryClient = useQueryClient();
const { mutate, isPending } = useMutation({
mutationFn: title => fetch('/api/todos', { method: 'POST', body: JSON.stringify({ title }) }),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
});
if (status === 'pending') return <p>Loading…</p>;
if (status === 'error' && !data) return <button onClick={refetch}>Retry</button>;
return (
<>
{isFetching && <Spinner />}
<ul>{data.map(todo => <li key={todo.id}>{todo.title}</li>)}</ul>
<button disabled={isPending} onClick={() => mutate('New todo')}>Add</button>
</>
);
}API
<QueryClientProvider client={client}>
Provides a client from createQueryClient() to the tree.
useQuery(options)
Everything client.watchQuery accepts. Returns the
query state plus:
| Field | |
|---|---|
| isPending / isSuccess / isError | derived from status |
| refetch() | forces a fetch; never rejects, so it is safe as an onClick |
The observer is rebuilt when the query key or
enabledchanges — and only then.queryFnis a new closure on every render, so anything it depends on must appear inqueryKey, exactly as the cache requires anyway. Read those inputs back out of the{ queryKey }argument rather than closing over them.
const { data } = useQuery({
queryKey: ['todos', { page }],
queryFn: ({ queryKey, signal }) =>
fetch(`/api/todos?page=${queryKey[1].page}`, { signal }).then(r => r.json()),
});Two components with the same key share one request and one cache entry. On
unmount the hook releases its observer, and the entry is evicted after gcTime.
useMutation(options)
Everything createMutation accepts. Callbacks are read fresh on every call, so
they may close over current props. Returns the mutation state plus:
| Field | |
|---|---|
| isIdle / isPending / isSuccess / isError | derived from status |
| mutate(variables) | fire-and-forget; swallows the rejection — the error is on the state |
| mutateAsync(variables) | returns the promise and rethrows, for await at the call site |
| reset() | back to idle |
useQueryClient()
The client from the nearest provider — for setQueryData, invalidateQueries
and the rest inside a callback.
Reading a query without this package
A query observer is { getState, subscribe }, so
@corndev/store-react's
useSelector reads one directly:
const status = useSelector(state => state.status, { store: observer });Neither package depends on the other. Use whichever you already have.
Licence
MIT © Corndog Development LLC
