Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: update tanstack query integration documentation to the new api #2294

Merged
merged 5 commits into from
Dec 10, 2023
Merged
Changes from 1 commit
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
208 changes: 142 additions & 66 deletions docs/extensions/query.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ keywords: tanstack,query

[TanStack Query](https://tanstack.com/query/) provides a set of functions for managing async state (typically external data).

From the [Overview docs](https://tanstack.com/query/v4/docs/overview):
From the [Overview docs](https://tanstack.com/query/v5/docs/overview):

> React Query is often described as the missing data-fetching library for React, but in more technical terms, it makes **fetching, caching, synchronizing and updating server state** in your React applications a breeze.

[jotai-tanstack-query](https://github.com/jotai-labs/jotai-tanstack-query) is a Jotai extension library for TanStack Query. It provides a wonderful interface with all of the TanStack Query features, providing you the ability to use those features in combination with your existing Jotai state.

### Support

jotai-tanstack-query currently supports TanStack Query v5.

### Install

In addition to `jotai`, you have to install `jotai-tanstack-query` and `@tanstack/query-core` to use the extension.
Expand All @@ -23,37 +27,32 @@ yarn add jotai-tanstack-query @tanstack/query-core

### Exported functions

- `atomsWithQuery` for [QueryObserver](https://tanstack.com/query/v4/docs/reference/QueryObserver)
- `atomsWithInfiniteQuery` for [InfiniteQueryObserver](https://tanstack.com/query/v4/docs/reference/InfiniteQueryObserver)
- `atomsWithMutation` for MutationObserver
- `atomWithQuery` for [useQuery](https://tanstack.com/query/v5/docs/react/reference/useQuery)
- `atomWithInfiniteQuery` for [useInfiniteQuery](https://tanstack.com/query/v5/docs/react/reference/useInfiniteQuery)
- `atomWithMutation` for [useMutation](https://tanstack.com/query/v5/docs/react/reference/useMutation)
- `atomWithSuspenseQuery` for [useSuspenseQuery](https://tanstack.com/query/v5/docs/react/reference/useSuspenseQuery)
- `atomWithSuspenseInfiniteQuery` for [useSuspenseInfiniteQuery](https://tanstack.com/query/v5/docs/react/reference/useSuspenseInfiniteQuery)
- `atomWithMutationState` for [useMutationState](https://tanstack.com/query/v5/docs/react/reference/useMutationState)

All three functions follow the same signature.
All functions follow the same signature.

```ts
const [dataAtom, statusAtom] = atomsWithSomething(getOptions, getQueryClient)
const dataAtom = atomsWithSomething(getOptions, getQueryClient)
```

The first `getOptions` parameter is a function that returns an input to the observer.
The second optional `getQueryClient` parameter is a function that return [QueryClient](https://tanstack.com/query/v4/docs/reference/QueryClient).

The return values have two atoms.
The first one is called `dataAtom` and it's an atom for the data from the observer. `dataAtom` requires Suspense.
The second one is called `statusAtom` and it's an atom for the full result from the observer. `statusAtom` doesn't require Suspense and won't throw errors to Error Boundary.
The data from the observer is also included in `statusAtom`,
so if you don't use Suspense and Error Boundary, you don't need to use `dataAtom`.

### `atomsWithQuery` usage
The second optional `getQueryClient` parameter is a function that return [QueryClient](https://tanstack.com/query/v5/docs/reference/QueryClient).

`atomsWithQuery` creates new atoms that implement a standard [`Query`](https://tanstack.com/query/v4/docs/guides/queries) from TanStack Query.
### atomWithQuery usage

> A query is a declarative dependency on an asynchronous source of data that is tied to a unique key. A query can be used with any Promise based method (including GET and POST methods) to fetch data from a server.
`atomWithQuery` creates a new atom that implements a standard [`Query`](https://tanstack.com/query/v5/docs/react/guides/queries) from TanStack Query.

```jsx
import { atom, useAtom } from 'jotai'
import { atomsWithQuery } from 'jotai-tanstack-query'
import { atomWithQuery } from 'jotai-tanstack-query'

const idAtom = atom(1)
const [userAtom] = atomsWithQuery((get) => ({
const userAtom = atomsWithQuery((get) => ({
kalijonn marked this conversation as resolved.
Show resolved Hide resolved
queryKey: ['users', get(idAtom)],
queryFn: async ({ queryKey: [, id] }) => {
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
Expand All @@ -62,91 +61,169 @@ const [userAtom] = atomsWithQuery((get) => ({
}))

const UserData = () => {
const [data] = useAtom(userAtom)
const [{ data, isPending, isError }] = useAtom(userAtom)

if (isPending) return <div>Loading...</div>
if (isError) return <div>Error</div>

return <div>{JSON.stringify(data)}</div>
}
```

### `atomsWithInfiniteQuery` usage
### atomWithInfiniteQuery usage

`atomsWithInfiniteQuery` is very similar to `atomsWithQuery`, however it is for an `InfiniteQuery`, which is used for data that is meant to be paginated. You can [read more about Infinite Queries here](https://tanstack.com/query/v4/docs/guides/infinite-queries).
`atomWithInfiniteQuery` is very similar to `atomWithQuery`, however it is for an `InfiniteQuery`, which is used for data that is meant to be paginated. You can [read more about Infinite Queries here](https://tanstack.com/query/v5/docs/guides/infinite-queries).

> Rendering lists that can additively "load more" data onto an existing set of data or "infinite scroll" is also a very common UI pattern. React Query supports a useful version of useQuery called useInfiniteQuery for querying these types of lists.

A notable difference between a standard query atom is the additional option `getNextPageParam` and `getPreviousPageParam`, which is what you'll use to instruct the query on how to fetch any additional pages.

```jsx
import { atom, useAtom } from 'jotai'
import { atomsWithInfiniteQuery } from 'jotai-tanstack-query'
import { atomWithInfiniteQuery } from 'jotai-tanstack-query'

const idAtom = atom(1)
const [userAtom] = atomsWithInfiniteQuery((get) => ({
queryKey: ['users', get(idAtom)],
queryFn: async ({ queryKey: [, id] }) => {
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
const postsAtom = atomWithInfiniteQuery(() => ({
queryKey: ['posts'],
queryFn: async ({ pageParam }) => {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${pageParam}`)
return res.json()
},
// infinite queries can support paginated fetching
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
getNextPageParam: (lastPage, allPages, lastPageParam) => lastPageParam + 1,
initialPageParam: 1,
}))

const UserData = () => {
const [data] = useAtom(userAtom)
return data.pages.map((userData, index) => (
<div key={index}>{JSON.stringify(userData)}</div>
))
}
```

#### Fetching pages and refetching
const Posts = () => {
const [{ data, fetchNextPage, isPending, isError, isFetching }] =
useAtom(postsAtom)

Using the same atom as in the above example, we can dispatch an action to `userAtom`.
if (isPending) return <div>Loading...</div>
if (isError) return <div>Error</div>

```js
const UserData = () => {
const [data, dispatch] = useAtom(userAtom)
const handleFetchNextPage = () => dispatch({ type: 'fetchNextPage' })
const handleFetchPreviousPage = () => dispatch({ type: 'fetchPreviousPage' })
return (
<>
{data.pages.map((page, index) => (
<div key={index}>
{page.map((post: any) => (
<div key={post.id}>{post.title}</div>
))}
</div>
))}
<button onClick={() => fetchNextPage()}>Next</button>
</>
)
}
```

### `atomsWithMutation` usage
### atomWithMutation usage

`atomsWithMutation` creates new atoms that implement a standard [`Mutation`](https://tanstack.com/query/v4/docs/guides/mutations) from TanStack Query.
`atomWithMutation` creates a new atom that implements a standard [`Mutation`](https://tanstack.com/query/v5/docs/guides/mutations) from TanStack Query.

> Unlike queries, mutations are typically used to create/update/delete data or perform server side-effects.

```jsx
kalijonn marked this conversation as resolved.
Show resolved Hide resolved
import { atom, useAtom } from 'jotai'
import { atomsWithMutation } from 'jotai-tanstack-query'

const idAtom = atom(1)
const [, postAtom] = atomsWithMutation((get) => ({
const postAtom = atomWithMutation(() => ({
mutationKey: ['posts'],
mutationFn: async ({ title, body }) => {
mutationFn: async ({ title }: { title: string }) => {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts`, {
method: 'POST',
body: JSON.stringify({ title, body, userId: get(idAtom) }),
headers: { 'Content-type': 'application/json; charset=UTF-8' },
body: JSON.stringify({
title,
body: 'body',
userId: 1,
}),
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
})
const data = await res.json()
return data
},
}))

const PostData = () => {
const [post, mutate] = useAtom(postAtom)
const Posts = () => {
const [{ mutate, status }] = useAtom(postAtom)
return (
<div>
<button onClick={() => mutate([{ title: 'foo', body: 'bar' }])}>
Click me
</button>
<div>{JSON.stringify(post)}</div>
<button onClick={() => mutate({ title: 'foo' })}>Click me</button>
<pre>{JSON.stringify(status, null, 2)}</pre>
</div>
)
}
```

### atomWithMutationState usage

`atomWithMutationState` creates a new atom that gives you access to all mutations in the [`MutationCache`](https://tanstack.com/query/v5/docs/react/reference/useMutationState).

```jsx
const mutationStateAtom = atomWithMutationState((get) => ({
filters: {
mutationKey: ['posts'],
},
}))
```

### Suspense

jotai-tanstack-query can also be used with React's Suspense.

### atomWithSuspenseQuery usage

```jsx
import { atom, useAtom } from 'jotai'
import { atomWithSuspenseQuery } from 'jotai-tanstack-query'

const idAtom = atom(1)
const userAtom = atomWithSuspenseQuery((get) => ({
queryKey: ['users', get(idAtom)],
queryFn: async ({ queryKey: [, id] }) => {
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
return res.json()
},
}))

const UserData = () => {
const [{ data }] = useAtom(userAtom)

return <div>{JSON.stringify(data)}</div>
}
```

### atomWithSuspenseInfiniteQuery usage

```jsx
import { atom, useAtom } from 'jotai'
import { atomWithSuspenseInfiniteQuery } from 'jotai-tanstack-query'

const postsAtom = atomWithSuspenseInfiniteQuery(() => ({
queryKey: ['posts'],
queryFn: async ({ pageParam }) => {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${pageParam}`)
return res.json()
},
getNextPageParam: (lastPage, allPages, lastPageParam) => lastPageParam + 1,
initialPageParam: 1,
}))

const Posts = () => {
const [{ data, fetchNextPage, isPending, isError, isFetching }] =
useAtom(postsAtom)

return (
<>
{data.pages.map((page, index) => (
<div key={index}>
{page.map((post: any) => (
<div key={post.id}>{post.title}</div>
))}
</div>
))}
<button onClick={() => fetchNextPage()}>Next</button>
</>
)
}
```

### Referencing the same instance of Query Client in your project

Perhaps you have some custom hooks in your project that utilises the `useQueryClient()` hook to obtain the `QueryClient` object and call its methods.
Expand Down Expand Up @@ -176,7 +253,7 @@ import {
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'
import { atomsWithQuery, queryClientAtom } from 'jotai-tanstack-query'
import { atomWithQuery, queryClientAtom } from 'jotai-tanstack-query'

const queryClient = new QueryClient()

Expand All @@ -202,7 +279,7 @@ export const App = () => {
)
}

export const [todosAtom] = atomsWithQuery((get) => {
export const todosAtom = atomWithQuery((get) => {
return {
queryKey: ['todos'],
queryFn: () => fetch('/todos'),
Expand All @@ -228,11 +305,10 @@ export const useTodoMutation = () => {

### SSR support

All atoms can be used within the context of a server side rendered app, such as a next.js app or Gatsby app. You can [use both options](https://tanstack.com/query/v4/docs/guides/ssr) that React Query supports for use within SSR apps, [hydration](https://tanstack.com/query/v4/docs/guides/ssr#using-hydration) or [`initialData`](https://tanstack.com/query/v4/docs/guides/ssr#using-initialdata).
All atoms can be used within the context of a server side rendered app, such as a next.js app or Gatsby app. You can [use both options](https://tanstack.com/query/v5/docs/guides/ssr) that React Query supports for use within SSR apps, [hydration](https://tanstack.com/query/v5/docs/react/guides/ssr#using-the-hydration-apis) or [`initialData`](https://tanstack.com/query/v5/docs/react/guides/ssr#get-started-fast-with-initialdata).

### Error handling

With `dataAtom`,
Fetch error will be thrown and can be caught with ErrorBoundary.
Refetching may recover from a temporary error.

Expand All @@ -250,7 +326,7 @@ $ pnpm add @tanstack/react-query-devtools
$ yarn add @tanstack/react-query-devtools
```

All you have to do is put the `<ReactQueryDevtools />` in the `<QueryClientProvider />`.
All you have to do is put the `<ReactQueryDevtools />` within `<QueryClientProvider />`.

```tsx
import {
Expand Down
Loading