Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 228 additions & 3 deletions docs/reference/plugins/tanstack-query.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import FineGrainedOptimistic from './_fine-grained-optimistic.md';
If you're looking for generating hooks for [SWR](https://swr.vercel.app/), please checkout the [`@zenstackhq/swr`](./swr) plugin.
:::

The `@zenstackhq/tanstack-query` plugin generates [Tanstack Query](https://tanstack.com/query/latest) hooks that call into the CRUD services provided by the [server adapters](../../category/server-adapters). The plugin currently supports React and Svelte. Vue support is coming soon.
The `@zenstackhq/tanstack-query` plugin generates [TanStack Query](https://tanstack.com/query/latest) hooks that call into the CRUD services provided by the [server adapters](../../category/server-adapters). The plugin currently supports React, Vue, Svelte, and Angular (v5 only).


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).

Expand All @@ -33,8 +34,8 @@ npm install --save-dev @zenstackhq/tanstack-query
| Name | Type | Description | Required | Default |
| -------- | ------- | ------------------------------------------------------- | -------- | ------- |
| output | String | Output directory (relative to the path of ZModel) | Yes | |
| target | String | Target framework to generate for. Choose from "react", "vue", and "svelte". | Yes | |
| version | String | Version of TanStack Query to generate for. Choose from "v4" and "v5". | No | v5 |
| target | String | Target framework to generate for. Choose from "react", "vue", "svelte", "angular". | Yes | |
| version | String | Version of TanStack Query to generate for. Choose from "v4" and "v5". Angular supports only "v5" | No | v5 |
| portable | Boolean | Include TypeScript types needed to compile the generated code in the output directory. Useful when you output into another project that doesn't reference Prisma and ZenStack. You'll still need to install the "@zenstackhq/tanstack-query" package in that project. | No | false |

### Hooks Signature
Expand Down Expand Up @@ -181,6 +182,40 @@ provideHooksContext({
```
</TabItem>

<TabItem value="angular" label="Angular">

```typescript title='app.config.ts'
import {
provideTanStackQuery,
QueryClient,
} from '@tanstack/angular-query-experimental';
import { provideAngularQueryContext } from '@/lib/hooks';
import type { FetchFn } from '@zenstackhq/tanstack-query/runtime';

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

export const appConfig: ApplicationConfig = {
providers: [
provideTanStackQuery(new QueryClient()),
provideAngularQueryContext({
endpoint: 'http://localhost:3000/v1/api/rpc',
fetch: myFetch,
logging: true,
}),
],
};

```

</TabItem>

</Tabs>

:::info Notes about Next.js app router
Expand Down Expand Up @@ -339,6 +374,44 @@ export default config;

</TabItem>


<TabItem value="angular" label="Angular">

```typescript title='src/app/posts/posts.component.ts'

import {
ApplicationConfig,
provideBrowserGlobalErrorListeners,
provideZonelessChangeDetection,
} from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { routes } from './app.routes';
import { provideAngularQueryContext } from '@/lib/hooks';
import {
provideTanStackQuery,
QueryClient,
withDevtools,
} from '@tanstack/angular-query-experimental';


export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideZonelessChangeDetection(),
provideTanStackQuery(new QueryClient(), withDevtools()),
provideAngularQueryContext({
endpoint: 'http://localhost:3000/v1/api/rpc',
}),
provideRouter(routes, withComponentInputBinding()),
],
};


```

</TabItem>


</Tabs>

#### Using Query and Mutation Hooks
Expand Down Expand Up @@ -473,6 +546,91 @@ const { data: posts } = useFindManyPost(queryParams);
</div>
```

</TabItem>

<TabItem value="angular" label="Angular">

```typescript title='src/app/posts/posts.component.ts'
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
input,
signal,
} from '@angular/core';
import { useCreatePost, useFindManyPost } from '@lib/hooks/generatedAPI';

@Component({
selector: 'app-posts-component',
standalone: true,
imports: [CommonModule],
template: ` <div>
<button (click)="onCreatePost()">Create</button>
@if (posts.data()) {
<ul>
@for (post of posts.data(); track post.id) {
<li>{{ post.title }} by {{ post.author.email }}</li>
}
</ul>
}
</div>
<div>
<p>Filtered Posts</p>
<input
#searchInput
type="text"
placeholder="Filter by title…"
[value]="search()"
(input)="search.set(searchInput.value)"
aria-label="Filter posts by title"
/>
@if (filteredPosts.data()) {
<ul>
@for (post of filteredPosts.data(); track post.id) {
<li>{{ post.title }} by {{ post.author.email }}</li>
}
</ul>
}
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PostsComponent {
id = input.required<string>();
search = signal('');

posts = useFindManyPost({
include: { author: true },
orderBy: { createdAt: 'desc' },
});

filteredPostsArgs = computed(() => {
const search = this.search();

return {
where: { title: { contains: search } },
include: { author: true },
orderBy: { createdAt: 'desc' } as const,
}
});

//For Reactivity in angular we have to pass the signal as callback
filteredPosts = useFindManyPost(() => this.filteredPostsArgs());

create = useCreatePost();

onCreatePost() {
this.create.mutate({
data: {
title: 'My awesome post',
authorId: this.id(),
},
});
}
}

```

</TabItem>
</Tabs>

Expand Down Expand Up @@ -679,6 +837,73 @@ Here's a quick example of using infinite query to load a list of posts with infi
</div>
```

</TabItem>

<TabItem value="angular" label="Angular">

Here's a quick example of using infinite query to load a list of posts with infinite pagination. See [Tanstack Query documentation](https://tanstack.com/query/v5/docs/framework/angular/examples/infinite-query-with-max-pages) for more details.

```ts title='src/app/posts/posts.component.ts'
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
signal,
} from '@angular/core';
import { useInfiniteFindManyPost } from '@/lib/hooks';

@Component({
selector: 'app-posts-component',
standalone: true,
imports: [CommonModule],
template: `
<div>
@if (postsInfinite.data(); as data) {
<ul>
@for (posts of data.pages; track $index) {
@for (post of posts; track post.id) {
<li>{{ post.title }} by {{ post.author.email }}</li>
}
}
</ul>
}
</div>

@if (postsInfinite.hasNextPage()) {
<button (click)="postsInfinite.fetchNextPage()">Load More</button>
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PostsComponent {
PAGE_SIZE = signal(10);

fetchArgs = computed(() => {
return {
include: { author: true },
orderBy: { createdAt: 'desc' as const },
take: this.PAGE_SIZE(),
};
});

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


```

</TabItem>
</Tabs>

Expand Down