@everystack/query
v0.4.1
Published
React Query hooks for PostgREST-compatible APIs
Readme
@everystack/query
React Query hooks for PostgREST-compatible APIs. Optional companion to @everystack/api — the client works standalone, this package adds React Query integration.
Install
pnpm add @everystack/query @tanstack/react-querySetup
import { createHooks } from '@everystack/query';
const { useResource, useResourceList, useMutation } = createHooks({
baseUrl: 'https://myapp.com/api',
headers: { 'X-Custom': 'value' },
getToken: () => localStorage.getItem('token'),
});Wrap your app with React Query's QueryClientProvider:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<MyApp />
</QueryClientProvider>
);
}Hooks
useResource
Fetch a single resource by filter. Returns a React Query result.
function PostPage({ slug }: { slug: string }) {
const { data, isLoading, error } = useResource('posts', {
slug: `eq.${slug}`,
});
if (isLoading) return <Text>Loading...</Text>;
if (error) return <Text>Error: {error.message}</Text>;
return <Text>{data.title}</Text>;
}The filters parameter uses PostgREST syntax — each key is a column, each value is operator.value:
useResource('users', { id: 'eq.123' });
useResource('posts', { status: 'eq.published', authorId: 'eq.abc' });Conditional fetching with enabled:
const { data } = useResource('profiles', { userId: `eq.${userId}` }, {
enabled: !!userId,
});useResourceList
Fetch a list of resources with filters, ordering, pagination, and column selection.
function PostList() {
const { data, isLoading } = useResourceList('posts', {
filters: { status: 'eq.published' },
order: 'createdAt.desc',
limit: 20,
offset: 0,
select: 'id,title,author(name)',
});
return data?.map(post => <PostCard key={post.id} post={post} />);
}useMutation
Create, update, or delete resources. Returns a React Query mutation result.
function CreatePost() {
const { mutateAsync, isPending } = useMutation('posts', 'POST');
async function handleSubmit(values: PostForm) {
const result = await mutateAsync({
title: values.title,
content: values.content,
});
// result is the created record
}
return <Button onPress={handleSubmit} disabled={isPending}>Create</Button>;
}PATCH and DELETE take { filters, body? }. filters is a PostgREST filter map that selects the rows, sent in the query string. It must name at least one filter: with none, mutateAsync rejects before any request is sent. The API refuses an unfiltered PATCH or DELETE as well.
// Update post 123
const updatePost = useMutation('posts', 'PATCH');
await updatePost.mutateAsync({ filters: { id: 'eq.123' }, body: { title: 'Updated' } });
// Delete post 123
const deletePost = useMutation('posts', 'DELETE');
await deletePost.mutateAsync({ filters: { id: 'eq.123' } });Before this change PATCH and DELETE sent the argument as the body and no filter. @everystack/api 0.4.5 and later refuse both; older APIs applied such a PATCH to every row the caller could write. Move the row selector into filters and the changed fields into body.
Query Keys
Query keys are automatically derived from the table name and parameters, enabling automatic cache invalidation:
useResource('posts', { id: 'eq.1' })→['posts', { id: 'eq.1' }]useResourceList('posts', { limit: 20 })→['posts', 'list', { limit: 20 }]
Invalidate with React Query's queryClient:
queryClient.invalidateQueries({ queryKey: ['posts'] });Peer Dependencies
| Package | Version |
|---------|---------|
| @tanstack/react-query | >=5.0.0 |
Part of everystack — a self-hosted application stack for Expo apps on AWS.
License
AGPL-3.0-only © Scalable Technology, Inc.
A commercial license is available for organizations that cannot or do not wish to comply with the AGPL-3.0 terms. For commercial licensing, contact [email protected].
