Gathering detailed insights and metrics for jotai-tanstack-query
Gathering detailed insights and metrics for jotai-tanstack-query
Gathering detailed insights and metrics for jotai-tanstack-query
Gathering detailed insights and metrics for jotai-tanstack-query
@tanstack/react-query
Hooks for managing, caching and syncing asynchronous and remote data in React
jotai
👻 Primitive and flexible state management for React
react-query
Hooks for managing, caching and syncing asynchronous and remote data in React
@tanstack/vue-query
Hooks for managing, caching and syncing asynchronous and remote data in Vue
npm install jotai-tanstack-query
85.1
Supply Chain
94.9
Quality
86
Maintenance
100
Vulnerability
100
License
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
220 Stars
85 Commits
14 Forks
5 Watching
1 Branches
11 Contributors
Updated on 24 Nov 2024
TypeScript (98.68%)
JavaScript (1.32%)
Cumulative downloads
Total Downloads
Last day
-11.9%
5,969
Compared to previous day
Last week
-3.5%
32,145
Compared to previous week
Last month
9.5%
139,520
Compared to previous month
Last year
275.9%
1,161,978
Compared to previous year
2
32
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.
jotai-tanstack-query currently supports TanStack Query v5.
In addition to jotai
, you have to install jotai-tanstack-query
and @tanstack/query-core
to use the extension.
1yarn add jotai-tanstack-query @tanstack/query-core
You can incrementally adopt jotai-tanstack-query
in your app. It's not an all or nothing solution. You just have to ensure you are using the same QueryClient instance. QueryClient Setup.
1# existing useQueryHook 2 const { data, isPending, isError } = useQuery({ 3 queryKey: ['todos'], 4 queryFn: fetchTodoList 5 }); 6 7# jotai-tanstack-query 8 const todosAtom = atomWithQuery(() => ({ 9 queryKey: ['todos'], 10 })) 11 12 const [{ data, isPending, isError }] = useAtom(todosAtom) 13
atomWithQuery
for useQueryatomWithInfiniteQuery
for useInfiniteQueryatomWithMutation
for useMutationatomWithSuspenseQuery
for useSuspenseQueryatomWithSuspenseInfiniteQuery
for useSuspenseInfiniteQueryatomWithMutationState
for useMutationStateAll functions follow the same signature.
1const dataAtom = atomWithSomething(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.
atomWithQuery
creates a new atom that implements a standard Query
from TanStack Query.
1import { atom, useAtom } from 'jotai' 2import { atomWithQuery } from 'jotai-tanstack-query' 3 4const idAtom = atom(1) 5const userAtom = atomWithQuery((get) => ({ 6 queryKey: ['users', get(idAtom)], 7 queryFn: async ({ queryKey: [, id] }) => { 8 const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`) 9 return res.json() 10 }, 11})) 12 13const UserData = () => { 14 const [{ data, isPending, isError }] = useAtom(userAtom) 15 16 if (isPending) return <div>Loading...</div> 17 if (isError) return <div>Error</div> 18 19 return <div>{JSON.stringify(data)}</div> 20}
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.
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.
1import { atom, useAtom } from 'jotai' 2import { atomWithInfiniteQuery } from 'jotai-tanstack-query' 3 4const postsAtom = atomWithInfiniteQuery(() => ({ 5 queryKey: ['posts'], 6 queryFn: async ({ pageParam }) => { 7 const res = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${pageParam}`) 8 return res.json() 9 }, 10 getNextPageParam: (lastPage, allPages, lastPageParam) => lastPageParam + 1, 11 initialPageParam: 1, 12})) 13 14const Posts = () => { 15 const [{ data, fetchNextPage, isPending, isError, isFetching }] = 16 useAtom(postsAtom) 17 18 if (isPending) return <div>Loading...</div> 19 if (isError) return <div>Error</div> 20 21 return ( 22 <> 23 {data.pages.map((page, index) => ( 24 <div key={index}> 25 {page.map((post: any) => ( 26 <div key={post.id}>{post.title}</div> 27 ))} 28 </div> 29 ))} 30 <button onClick={() => fetchNextPage()}>Next</button> 31 </> 32 ) 33}
atomWithMutation
creates a new atom that implements a standard Mutation
from TanStack Query.
Unlike queries, mutations are typically used to create/update/delete data or perform server side-effects.
1const postAtom = atomWithMutation(() => ({ 2 mutationKey: ['posts'], 3 mutationFn: async ({ title }: { title: string }) => { 4 const res = await fetch(`https://jsonplaceholder.typicode.com/posts`, { 5 method: 'POST', 6 body: JSON.stringify({ 7 title, 8 body: 'body', 9 userId: 1, 10 }), 11 headers: { 12 'Content-type': 'application/json; charset=UTF-8', 13 }, 14 }) 15 const data = await res.json() 16 return data 17 }, 18})) 19 20const Posts = () => { 21 const [{ mutate, status }] = useAtom(postAtom) 22 return ( 23 <div> 24 <button onClick={() => mutate({ title: 'foo' })}>Click me</button> 25 <pre>{JSON.stringify(status, null, 2)}</pre> 26 </div> 27 ) 28}
atomWithMutationState
creates a new atom that gives you access to all mutations in the MutationCache
.
1const mutationStateAtom = atomWithMutationState((get) => ({ 2 filters: { 3 mutationKey: ['posts'], 4 }, 5}))
jotai-tanstack-query can also be used with React's Suspense.
1import { atom, useAtom } from 'jotai' 2import { atomWithSuspenseQuery } from 'jotai-tanstack-query' 3 4const idAtom = atom(1) 5const userAtom = atomWithSuspenseQuery((get) => ({ 6 queryKey: ['users', get(idAtom)], 7 queryFn: async ({ queryKey: [, id] }) => { 8 const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`) 9 return res.json() 10 }, 11})) 12 13const UserData = () => { 14 const [{ data }] = useAtom(userAtom) 15 16 return <div>{JSON.stringify(data)}</div> 17}
1import { atom, useAtom } from 'jotai' 2import { atomWithSuspenseInfiniteQuery } from 'jotai-tanstack-query' 3 4const postsAtom = atomWithSuspenseInfiniteQuery(() => ({ 5 queryKey: ['posts'], 6 queryFn: async ({ pageParam }) => { 7 const res = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${pageParam}`) 8 return res.json() 9 }, 10 getNextPageParam: (lastPage, allPages, lastPageParam) => lastPageParam + 1, 11 initialPageParam: 1, 12})) 13 14const Posts = () => { 15 const [{ data, fetchNextPage, isPending, isError, isFetching }] = 16 useAtom(postsAtom) 17 18 return ( 19 <> 20 {data.pages.map((page, index) => ( 21 <div key={index}> 22 {page.map((post: any) => ( 23 <div key={post.id}>{post.title}</div> 24 ))} 25 </div> 26 ))} 27 <button onClick={() => fetchNextPage()}>Next</button> 28 </> 29 ) 30}
Perhaps you have some custom hooks in your project that utilises the useQueryClient()
hook to obtain the QueryClient
object and call its methods.
To ensure that you reference the same QueryClient
object, be sure to wrap the root of your project in a <Provider>
and initialise queryClientAtom
with the same queryClient
value you provided to QueryClientProvider
.
Without this step, useQueryAtom
will reference a separate QueryClient
from any hooks that utilise the useQueryClient()
hook to get the queryClient.
Alternatively, you can specify your queryClient
with getQueryClient
parameter.
In the example below, we have a mutation hook, useTodoMutation
and a query todosAtom
.
We included an initialisation step in our root <App>
node.
Although they reference methods same query key ('todos'
), the onSuccess
invalidation in useTodoMutation
will not trigger if the Provider
initialisation step was not done.
This will result in todosAtom
showing stale data as it was not prompted to refetch.
1import { Provider } from 'jotai/react' 2import { useHydrateAtoms } from 'jotai/react/utils' 3import { 4 useMutation, 5 useQueryClient, 6 QueryClient, 7 QueryClientProvider, 8} from '@tanstack/react-query' 9import { atomWithQuery, queryClientAtom } from 'jotai-tanstack-query' 10 11const queryClient = new QueryClient() 12 13const HydrateAtoms = ({ children }) => { 14 useHydrateAtoms([[queryClientAtom, queryClient]]) 15 return children 16} 17 18export const App = () => { 19 return ( 20 <QueryClientProvider client={queryClient}> 21 <Provider> 22 {/* 23 This Provider initialisation step is needed so that we reference the same 24 queryClient in both atomWithQuery and other parts of the app. Without this, 25 our useQueryClient() hook will return a different QueryClient object 26 */} 27 <HydrateAtoms> 28 <App /> 29 </HydrateAtoms> 30 </Provider> 31 </QueryClientProvider> 32 ) 33} 34 35export const todosAtom = atomWithQuery((get) => { 36 return { 37 queryKey: ['todos'], 38 queryFn: () => fetch('/todos'), 39 } 40}) 41 42export const useTodoMutation = () => { 43 const queryClient = useQueryClient() 44 45 return useMutation( 46 async (body: todo) => { 47 await fetch('/todo', { Method: 'POST', Body: body }) 48 }, 49 { 50 onSuccess: () => { 51 void queryClient.invalidateQueries(['todos']) 52 }, 53 onError, 54 } 55 ) 56}
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 that React Query supports for use within SSR apps, hydration or initialData
.
Fetch error will be thrown and can be caught with ErrorBoundary. Refetching may recover from a temporary error.
See a working example to learn more.
In order to use the Devtools, you need to install it additionally.
1$ npm i @tanstack/react-query-devtools 2# or 3$ pnpm add @tanstack/react-query-devtools 4# or 5$ yarn add @tanstack/react-query-devtools
All you have to do is put the <ReactQueryDevtools />
within <QueryClientProvider />
.
1import { 2 QueryClientProvider, 3 QueryClient, 4 QueryCache, 5} from '@tanstack/react-query' 6import { ReactQueryDevtools } from '@tanstack/react-query-devtools' 7import { queryClientAtom } from 'jotai-tanstack-query' 8 9const queryClient = new QueryClient({ 10 defaultOptions: { 11 queries: { 12 staleTime: Infinity, 13 }, 14 }, 15}) 16 17const HydrateAtoms = ({ children }) => { 18 useHydrateAtoms([[queryClientAtom, queryClient]]) 19 return children 20} 21 22export const App = () => { 23 return ( 24 <QueryClientProvider client={queryClient}> 25 <Provider> 26 <HydrateAtoms> 27 <App /> 28 </HydrateAtoms> 29 </Provider> 30 <ReactQueryDevtools /> 31 </QueryClientProvider> 32 ) 33}
All atom signatures have changed to be more consistent with TanStack Query.
v0.8.0 returns only a single atom, instead of a tuple of atoms, and hence the name change from atomsWithSomething
toatomWithSomething
.
1 2- const [dataAtom, statusAtom] = atomsWithSomething(getOptions, getQueryClient) 3+ const dataAtom = atomWithSomething(getOptions, getQueryClient) 4
In the previous version of jotai-tanstack-query
, the query atoms atomsWithQuery
and atomsWithInfiniteQuery
returned a tuple of atoms: [dataAtom, statusAtom]
. This design separated the data and its status into two different atoms.
dataAtom
was used to access the actual data (TData
).statusAtom
provided the status object (QueryObserverResult<TData, TError>
), which included additional attributes like isPending
, isError
, etc.In v0.8.0, they have been replaced by atomWithQuery
and atomWithInfiniteQuery
to return only a single dataAtom
. This dataAtom
now directly provides the QueryObserverResult<TData, TError>
, aligning it closely with the behavior of Tanstack Query's bindings.
To migrate to the new version, replace the separate dataAtom
and statusAtom
usage with the unified dataAtom
that now contains both data and status information.
1- const [dataAtom, statusAtom] = atomsWithQuery(/* ... */); 2- const [data] = useAtom(dataAtom); 3- const [status] = useAtom(statusAtom); 4 5+ const dataAtom = atomWithQuery(/* ... */); 6+ const [{ data, isPending, isError }] = useAtom(dataAtom);
Similar to atomsWithQuery
and atomsWithInfiniteQuery
, atomWithMutation
also returns a single atom instead of a tuple of atoms. The return type of the atom value is MutationObserverResult<TData, TError, TVariables, TContext>
.
1 2- const [, postAtom] = atomsWithMutation(/* ... */); 3- const [post, mutate] = useAtom(postAtom); // Accessing mutation status from post; and mutate() to execute the mutation 4 5+ const postAtom = atomWithMutation(/* ... */); 6+ const [{ data, error, mutate }] = useAtom(postAtom); // Accessing mutation result and mutate method from the same atom 7
No vulnerabilities found.
No security vulnerabilities found.