🕊️ A toolkit for ReactQuery that make ReactQuery hooks more reusable and typesafe
npm install react-query-kit---
- Manage queryKey in a type-safe way
- Make queryClient's operations clearly associated with custom ReactQuery hooks
- You can extract the TypeScript type of any custom ReactQuery hooks
- Middleware
English | 简体中文
- Installation
- Examples
- Usage
- createQuery
- createInfiniteQuery
- createSuspenseQuery
- createSuspenseInfiniteQuery
- createMutation
- router
- Middleware
- TypeScript
- Type inference
- Disabling Queries
- FAQ
- Migration
- Issues
- 🐛 Bugs
- 💡 Feature Requests
- LICENSE
This module is distributed via [npm][npm] which is bundled with [node][node] and
should be installed as one of your project's dependencies:
``bash`
$ npm i react-query-kitor
$ yarn add react-query-kit
If you still on React Query Kit v2? Check out the v2 docs here: https://github.com/liaoliao666/react-query-kit/tree/v2#readme.
- Basic
- Optimistic Updates
- Next.js
- Load-More & Infinite Scroll
`tsx
import { QueryClient, dehydrate } from '@tanstack/react-query'
import { createQuery } from 'react-query-kit'
type Data = { title: string; content: string }
type Variables = { id: number }
const usePost = createQuery({
queryKey: ['posts'],
fetcher: (variables: Variables): Promise => {
return fetch(/posts/${variables.id}).then(res => res.json())
},
// u can also pass middleware to cutomize this hook's behavior
use: [myMiddleware]
})
const variables = { id: 1 }
// example
export default function Page() {
// queryKey will be ['posts', { id: 1 }] if u passed variables
const { data } = usePost({ variables })
return (
console.log(usePost.getKey()) // ['posts']
console.log(usePost.getKey(variables)) // ['posts', { id: 1 }]
// nextjs example
export async function getStaticProps() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery(usePost.getFetchOptions(variables))
return {
props: {
dehydratedState: dehydrate(queryClient),
},
}
}
// usage outside of react component
const data = await queryClient.fetchQuery(usePost.getFetchOptions(variables))
// useQueries example
const queries = useQueries({
queries: [
usePost.getOptions(variables),
useUser.getOptions(),
],
})
// getQueryData
queryClient.getQueryData(usePost.getKey(variables)) // Data
// setQueryData
queryClient.setQueryData(usePost.getKey(variables), {...})
`
Options
- fetcher: (variables: TVariables, context: QueryFunctionContextQueryFunctionContext
- Required
- The function that the query will use to request data. And The second param is the of queryFn.variables?: TVariables
- variables
- Optional
- will be the frist param of fetcher and the last element of the queryKey arrayuse: Middleware[]
-
- Optional
- array of middleware functions (details)
Expose Methods
- fetcher: (variables: TVariables, context: QueryFunctionContextgetKey: (variables: TVariables) => QueryKey
- getOptions: (variables: TVariables) => UseQueryOptions
- getFetchOptions: (variables: TVariables) => ({ queryKey, queryFn, queryKeyHashFn })
-
`tsx
import { QueryClient, dehydrate } from '@tanstack/react-query'
import { createInfiniteQuery } from 'react-query-kit'
type Data = { projects: { id: string; name: string }[]; nextCursor: number }
type Variables = { active: boolean }
const useProjects = createInfiniteQuery({
queryKey: ['projects'],
fetcher: (variables: Variables, { pageParam }): Promise => {
return fetch(
/projects?cursor=${pageParam}?active=${variables.active}
).then(res => res.json())
},
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
initialPageParam: 0,
})
const variables = { active: true }
// example
export default function Page() {
// queryKey equals to ['projects', { active: true }]
const { data, fetchNextPage, hasNextPage, isFetching, isFetchingNextPage } =
useProjects({ variables })
return (
{project.name}
// nextjs example
export async function getStaticProps() {
const queryClient = new QueryClient()
await queryClient.prefetchInfiniteQuery(
useProjects.getFetchOptions(variables)
)
return {
props: {
dehydratedState: dehydrate(queryClient),
},
}
}
// usage outside of react component
const data = await queryClient.fetchInfiniteQuery(
useProjects.getFetchOptions(variables)
)
`
Options
- fetcher: (variables: TVariables, context: QueryFunctionContextQueryFunctionContext
- Required
- The function that the query will use to request data. And The second param is the of queryFn.variables?: TVariables
- variables
- Optional
- will be the frist param of fetcher and the last element of the queryKey arrayuse: Middleware[]
-
- Optional
- array of middleware functions (details)
Expose Methods
- fetcher: (variables: TVariables, context: QueryFunctionContextgetKey: (variables: TVariables) => QueryKey
- getOptions: (variables: TVariables) => UseInfiniteQueryOptions
- getFetchOptions: (variables: TVariables) => ({ queryKey, queryFn, queryKeyHashFn, getNextPageParam, getPreviousPageParam, initialPageParam })
-
This has the same effect as setting the suspense option to true in the query config, but it works better in TypeScript, because data is guaranteed to be defined (as errors and loading states are handled by Suspense- and ErrorBoundaries).
`ts
import { createSuspenseQuery } from 'react-query-kit'
createSuspenseQuery({
...options,
})
// equals to
createQuery({
...options,
enabled: true,
suspense: true,
throwOnError: true,
})
`
`ts
import { createSuspenseInfiniteQuery } from 'react-query-kit'
createSuspenseInfiniteQuery({
...options,
})
// equals to
createInfiniteQuery({
...options,
enabled: true,
suspense: true,
throwOnError: true,
})
`
`tsx
import { createMutation } from 'react-query-kit'
const useAddTodo = createMutation({
mutationFn: async (variables: { title: string; content: string }) =>
fetch('/post', {
method: 'POST',
body: JSON.stringify(variables),
}).then(res => res.json()),
onSuccess(data, variables, context) {
// do somethings
},
})
function App() {
const mutation = useAddTodo({
onSettled: (data, error, variables, context) => {
// Error or success... doesn't matter!
},
})
return (
{mutation.isSuccess ?
onClick={() => {
mutation.mutate({ title: 'Do Laundry', content: 'content...' })
}}
>
create Todo
>
)}
// usage outside of react component
useAddTodo.mutationFn({ title: 'Do Laundry', content: 'content...' })
`
Options
- use: Middleware[]
- Optional
- array of middleware functions (details)
Expose Methods
- getKey: () => MutationKeygetOptions: () => UseMutationOptions
- mutationFn: MutationFunction
-
router which allow you to create a shape of your entire API
`tsx
import { router } from 'react-query-kit'
const post = router(post, {/posts/${variables.id}
byId: router.query({
fetcher: (variables: { id: number }) =>
fetch().then(res => res.json()),
use: [myMiddleware],
}),
list: router.infiniteQuery({
fetcher: (_variables, { pageParam }) =>
fetch(/posts/?cursor=${pageParam}).then(res => res.json()),
getNextPageParam: lastPage => lastPage.nextCursor,
initialPageParam: 0,
}),
add: router.mutation({
mutationFn: async (variables: { title: string; content: string }) =>
fetch('/posts', {
method: 'POST',
body: JSON.stringify(variables),
}).then(res => res.json()),
}),
// nest router
command: {
report: router.mutation({ mutationFn }),
promote: router.mutation({ mutationFn }),
},
})
// get root key
post.getKey() // ['post']
// hooks
post.byId.useQuery({ variables: { id: 1 } })
post.byId.useSuspenseQuery({ variables: { id: 1 } })
post.list.useInfiniteQuery()
post.list.useSuspenseInfiniteQuery()
post.add.useMutation()
post.command.report.useMutation()
// expose methods
post.byId.getKey({ id: 1 }) // ['post', 'byId', { id: 1 }]
post.byId.getFetchOptions({ id: 1 })
post.byId.getOptions({ id: 1 })
post.byId.fetcher({ id: 1 })
post.add.getKey() // ['post', 'add']
post.add.getOptions()
post.add.mutationFn({ title: 'title', content: 'content' })
// infer types
type Data = inferData
type FnData = inferFnData
type Variables = inferVariables
type Error = inferError
`
`ts
import { router } from 'react-query-kit'
const user = router(user, {})post
const post = router(, {})
const k = {
user,
post,
}
`
type Router = (key: string | unknown[], config: TConfig) => TRouter
Expose Methods
- querycreateQuery
Similar to but without option queryKeyinfiniteQuery
- createInfiniteQuery
Similar to but without option queryKeymutation
- createMutation
Similar to but without option mutationKey
This feature is inspired by the Middleware feature from SWR. The middleware feature is a new addition in ReactQueryKit 1.5.0 that enables you to execute logic before and after hooks.
Middleware receive the hook and can execute logic before and after running it. If there are multiple middleware, each middleware wraps the next middleware. The last middleware in the list will receive the original hook.
`ts
import { QueryClient } from '@tanstack/react-query'
import { Middleware, MutationHook, QueryHook, getKey } from 'react-query-kit'
const logger: Middleware
return options => {
const log = useLogger()
const fetcher = (variables, context) => {
log(context.queryKey, variables)
return options.fetcher(variables, context)
}
return useQueryNext({
...options,
fetcher,
})
}
}
const useUser = createQuery({
use: [logger],
})
// global middlewares
const queryMiddleware: Middleware
return options => {
// u can also get queryKey via function getKey
const fullKey = getKey(options.queryKey, options.variables)
// ...
return useQueryNext(options)
}
}
const mutationMiddleware: Middleware
return options => {
// ...
return useMutationNext(options)
}
}
const queryClient = new QueryClient({
defaultOptions: {
queries: {
use: [queryMiddleware],
},
mutations: {
use: [mutationMiddleware],
},
},
})
`
Middleware will be merged from superior. For example:
`jsx
const queryClient = new QueryClient({
defaultOptions: {
queries: {
use: [a],
},
},
})
const useSomething = createQuery({
use: [b],
})
useSomething({ use: [c] })
`
is equivalent to:
`js`
createQuery({ use: [a, b, c] })
Each middleware wraps the next middleware, and the last one just wraps the useQuery. For example:
`jsx`
createQuery({ use: [a, b, c] })
The order of middleware executions will be a → b → c, as shown below:
`plaintext`
enter a
enter b
enter c
useQuery()
exit c
exit b
exit a
In ReactQuery v5, the QueryClient will be the second argument to useQuery and useMutation. If u have multiple QueryClient in global, u should receive QueryClient in middleware hook.
`ts
const useSomething = createQuery({
use: [
function myMiddleware(useQueryNext) {
// u should receive queryClient as the second argument here
return (options, queryClient) => {
const client = useQueryClient(queryClient)
// ...
return useQueryNext(options, queryClient)
}
},
],
})
// if u need to pass an another QueryClient
useSomething({...}, anotherQueryClient)
`
By default, ReactQueryKit will also infer the types of data and variables from fetcher, so you can have the preferred types automatically.
`ts
type Data = { title: string; content: string }
type Variables = { id: number }
const usePost = createQuery({
queryKey: ['posts'],
fetcher: (variables: Variables): Promise => {
return fetch(/posts/${variables}).then(res => res.json())
},
})
// data will be inferred as Data | undefined.variables
// will be inferred as Variables.`
const { data } = usePost({ variables: { id: 1 } })
You can also explicitly specify the types for fetcher‘s variables and data.
`ts
type Data = { title: string; content: string }
type Variables = { id: number }
const usePost = createQuery({
queryKey: ['posts'],
fetcher: variables => {
return fetch(/posts/${variables}).then(res => res.json())
},
})
// data will be inferred as Data | undefined.error
// will be inferred as Error | nullvariables
// will be inferred as Variables.`
const { data, error } = usePost({ variables: { id: 1 } })
You can extract the TypeScript type of any custom hook with inferData or inferVariables
`ts
import { inferData, inferFnData, inferError, inferVariables, inferOptions } from 'react-query-kit'
const useProjects = createInfiniteQuery(...)
inferData
inferFnData
inferVariables
inferError
inferOptions
`
To disable queries, you can pass skipToken as the option variables to your custom query. This will prevent the query from being executed.
`ts
import { skipToken } from '@tanstack/react-query'
const [name, setName] = useState
const result = usePost({
variables: id ? { id: id } : skipToken,
})
// and for useQueries example
const queries = useQueries({
queries: [usePost.getOptions(id ? { id: id } : skipToken)],
})
`
getFetchOptions would only return necessary options, while options like staleTime and retry would be omited
ReactQueryKit would automatically converts fetcher to queryFn, as shown below:
`ts
const useTest = createQuery({
queryKey: ['test'],
fetcher: (variables, context) => {
// ...
},
})
// => useTest.getOptions(variables):
// {
// queryKey: ['test', variables],
// queryFn: (context) => fetcher(variables, context)
// }
`
Upgrading from ReactQueryKit 2 → ReactQueryKit 3
`diff``
createQuery({
- primaryKey: 'posts',
- queryFn: ({ queryKey: [_primaryKey, variables] }) => {},
+ queryKey: ['posts'],
+ fetcher: variables => {},
})
What you benefit from ReactQueryKit 3
- Support hierarchical key
- Support infer the types of fetcher, you can enjoy the preferred types automatically.
- Support to create a shape of your entire API
_Looking to contribute? Look for the [Good First Issue][good-first-issue]
label._
Please file an issue for bugs, missing documentation, or unexpected behavior.
[See Bugs][bugs]
Please file an issue to suggest new features. Vote on feature requests by adding
a 👍. This helps maintainers prioritize what to work on.
[See Feature Requests][requests]
MIT
[npm]: https://www.npmjs.com
[node]: https://nodejs.org
[bugs]: https://github.com/liaoliao666/react-query-kit/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Acreated-desc+label%3Abug
[requests]: https://github.com/liaoliao666/react-query-kit/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3Aenhancement
[good-first-issue]: https://github.com/liaoliao666/react-query-kit/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3Aenhancement+label%3A%22good+first+issue%22