Skip to main content

@zenstackhq/tanstack-query

info

If you're looking for generating hooks for SWR, please checkout the @zenstackhq/swr plugin.

The @zenstackhq/tanstack-query plugin generates Tanstack Query hooks that call into the CRUD services provided by the server adapters. The plugin currently supports React and Svelte. Vue support is coming soon.

The hooks syntactically mirror the APIs of a standard Prisma client, including the function names and shapes of parameters (hooks directly use types generated by Prisma).

To use the generated hooks, you need to install "tanstack-query" for the target framework with version 4.0.0 or above.

Options

NameTypeDescriptionRequiredDefault
outputStringOutput directory (relative to the path of ZModel)Yes
targetStringTarget framework to generate for. Choose from "react" and "svelte".Yes
useSuperJsonBooleanUse superjson for data serializationNofalse

Context Provider

The plugin generates a context provider which you can use to configure the behavior of the hooks. The following options are available on the provider:

  • endpoint

    The endpoint to use for the queries. Defaults to "/api/model".

  • fetch

    A custom fetch function to use for the queries. Defaults to the browser's built-in fetch.

Example for using the context provider:

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { FetchFn, Provider as ZenStackHooksProvider } from '../lib/hooks';

// custom fetch function that adds a custom header
const myFetch: FetchFn = (url, options) => {
options = options ?? {};
options.headers = {
...options.headers,
'x-my-custom-header': 'hello world',
};
return fetch(url, options);
};

const queryClient = new QueryClient();

function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
return (
<QueryClientProvider client={queryClient}>
<ZenStackHooksProvider value={{ endpoint: '/api/model', fetch: myFetch }}>
<AppContent />
</ZenStackHooksProvider>
</QueryClientProvider>
);
}

export default MyApp;

Example

Here's a quick example with a ReactJs blogging app. You can find a fully functional Todo app example here.

Schema

/schema.zmodel
plugin hooks {
provider = '@zenstackhq/tanstack-query'
output = "./src/lib/hooks"
target = "react"
}

model User {
id String @id @default(cuid())
email String
posts Post[]

// everyone can signup, and user profile is also publicly readable
@@allow('create,read', true)
}

model Post {
id String @id @default(cuid())
title String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String

// author has full access
@@allow('all', auth() == author)

// logged-in users can view published posts
@@allow('read', auth() != null && published)
}

App Setup


import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Provider as ZenStackHooksProvider } from '../lib/hooks';

const queryClient = new QueryClient();

function App({ Component, pageProps: { session, ...pageProps } }: AppProps) {
return (
<QueryClientProvider client={queryClient}>
<ZenStackHooksProvider value={{ endpoint: '/api/model' }}>
<AppContent />
</ZenStackHooksProvider>
</QueryClientProvider>
);
}

Using Query and Mutation Hooks

/src/components/Posts.tsx
import type { Post } from '@prisma/client';
import { useFindManyPost, useCreatePost } from '../lib/hooks';

// post list component
const Posts = ({ userId }: { userId: string }) => {
const create = useCreatePost();

// list all posts that're visible to the current user, together with their authors
const { data: posts } = useFindManyPost({
include: { author: true },
orderBy: { createdAt: 'desc' },
});

async function onCreatePost() {
create.mutate({
data: {
title: 'My awesome post',
authorId: userId,
},
});
}

return (
<>
<button onClick={onCreatePost}>Create</button>
<ul>
{posts?.map((post) => (
<li key={post.id}>
{post.title} by {post.author.email}
</li>
))}
</ul>
</>
);
};

Using Infinite Query

See Tanstack Query documentation for more details.

/src/components/Posts.tsx
import type { Post } from '@prisma/client';
import { useInfiniteFindManyPost } from '../lib/hooks';

// post list component with infinite loading
const Posts = () => {
const PAGE_SIZE = 10;

const fetchArgs = {
include: { author: true },
orderBy: { createdAt: 'desc' as const },
take: PAGE_SIZE,
};

const { data, fetchNextPage, hasNextPage } = useInfiniteFindManyPost(fetchArgs, {
getNextPageParam: (lastPage, pages) => {
if (lastPage.length < PAGE_SIZE) {
return undefined;
}
const fetched = pages.flatMap((item) => item).length;
return {
...fetchArgs,
skip: fetched,
};
},
});

return (
<>
<ul>
{data?.pages.map((posts, i) => (
<React.Fragment key={i}>
{posts?.map((post) => (
<li key={post.id}>
{post.title} by {post.author.email}
</li>
))}
</React.Fragment>
))}
</ul>
{hasNextPage && (
<button onClick={() => fetchNextPage()}>
Load more
</button>
)}
</>
);
};