@quanticjs/react-query
v7.3.1
Published
React Query integration for QuanticJS — typed hooks with auto client injection, smart defaults, pagination
Downloads
334
Readme
@quanticjs/react-query
TanStack Query hooks with auto-injected API client, typed errors, and declarative cache invalidation.
Install
pnpm add @quanticjs/react-core @quanticjs/react-query @tanstack/react-querySetup
import { QuanticProvider } from '@quanticjs/react-core';
import { QuanticQueryProvider } from '@quanticjs/react-query';
<QuanticProvider client={apiClient}>
<QuanticQueryProvider>
<App />
</QuanticQueryProvider>
</QuanticProvider>QuanticQueryProvider creates a QueryClient with smart defaults. To override:
import { createQueryClient } from '@quanticjs/react-query';
const queryClient = createQueryClient({
defaultOptions: { queries: { staleTime: 60_000 } },
});
<QuanticQueryProvider client={queryClient}>Default Query Options
| Option | Value | Rationale |
|---|---|---|
| retry | No retry on 4xx, 2 retries on 5xx | Client errors won't self-heal |
| retryDelay | ApiError.retryAfter * 1000 when set, else TanStack default backoff | Honors server Retry-After under rate limiting |
| staleTime | 30 seconds | Prevents redundant fetches |
| refetchOnWindowFocus | false | Opt-in, not opt-out |
| Mutation retry | false | Mutations should not auto-retry |
useApiQuery
Drop-in wrapper around useQuery that auto-injects the API client from QuanticProvider.
import { useApiQuery } from '@quanticjs/react-query';
const { data, isLoading, error } = useApiQuery(
['projects', id],
(client) => client.get<Project>(`/projects/${id}`),
);Signature
function useApiQuery<T>(
key: QueryKey,
fn: (client: ApiClient) => Promise<T>,
options?: Omit<UseQueryOptions<T, ApiError>, 'queryKey' | 'queryFn'>,
): UseQueryResult<T, ApiError>key— TanStack Query cache keyfn— receives the API client, returns a promiseoptions— anyUseQueryOptionsexceptqueryKey/queryFn(e.g.,enabled,staleTime,retry,select)
The error is always typed as ApiError — no casting needed.
Examples
// Conditional fetching
const { data } = useApiQuery(
['projects', id],
(client) => client.get<Project>(`/projects/${id}`),
{ enabled: !!id },
);
// Custom stale time
const { data } = useApiQuery(
['config'],
(client) => client.get<Config>('/config'),
{ staleTime: 5 * 60 * 1000 },
);
// With query params
const { data } = useApiQuery(
['items', { search, page }],
(client) => client.get<Items>('/items', { params: { search, page } }),
);useApiMutation
Wrapper around useMutation with auto-injected client and declarative cache invalidation.
import { useApiMutation } from '@quanticjs/react-query';
const mutation = useApiMutation(
(client, dto: CreateProjectDto) => client.post<Project>('/projects', dto),
{
invalidates: [['projects']],
onSuccess: (project) => navigate(`/projects/${project.id}`),
},
);
// Usage
mutation.mutate({ name: 'My Project' });Signature
function useApiMutation<TData, TVariables = void>(
fn: (client: ApiClient, variables: TVariables) => Promise<TData>,
options?: ApiMutationOptions<TData, TVariables>,
)ApiMutationOptions
| Property | Type | Description |
|---|---|---|
| invalidates | QueryKey[] | Query keys to invalidate on success |
| onSuccess | (data, variables) => void | Called after mutation + invalidation |
| onError | (error: ApiError, variables) => void | Called on failure |
| onSettled | (data, error, variables) => void | Called regardless of outcome |
| retry | boolean \| number | Override default retry behavior |
Cache invalidation runs automatically before onSuccess — queries are already refetching by the time your callback fires.
Examples
// No variables (void)
const logout = useApiMutation(
(client) => client.post<void>('/auth/logout'),
);
logout.mutate();
// File upload
const upload = useApiMutation(
(client, file: File) => {
const form = new FormData();
form.append('file', file);
return client.upload<UploadResult>('/files', form);
},
{ invalidates: [['files']] },
);
// Error handling
const save = useApiMutation(
(client, dto: UpdateDto) => client.put(`/items/${id}`, dto),
{
invalidates: [['items']],
onError: (error) => {
if (error.isConflict) toast.error('Item was modified by someone else');
},
},
);usePaginatedQuery
Manages page state internally with smooth transitions via keepPreviousData.
import { usePaginatedQuery } from '@quanticjs/react-query';
const {
data, // T[] — current page items
total, // number — total item count
page, // number — current page (1-based)
totalPages, // number
isLoading,
isFetching, // true during background refetch (page transition)
nextPage, // () => void
prevPage, // () => void
hasNextPage, // boolean
hasPrevPage, // boolean
setPage, // (page: number) => void
} = usePaginatedQuery(
['items'],
(client, { page, limit }) =>
client.get<PaginatedResponse<Item>>('/items', { params: { page, limit } }),
{ limit: 25 },
);Server Response Format
Your API must return:
interface PaginatedResponse<T> {
data: T[];
total: number;
}Options
| Property | Type | Default | Description |
|---|---|---|---|
| limit | number | 20 | Items per page |
| enabled | boolean | true | Enable/disable the query |
Exports
// Provider
QuanticQueryProvider, createQueryClient, type QuanticQueryProviderProps
// Hooks
useApiQuery
useApiMutation, type ApiMutationOptions
usePaginatedQuery, type PaginatedResponse, type UsePaginatedQueryOptions