What could you benefit from it
- Make
queryKey
strongly related withqueryFn
- Manage
queryKey
in a type-safe way - Generate a custom ReactQuery hook quickly
- Make
queryClient
's operations clearly associated with custom ReactQuery hooks - Set defaultOptions for custom ReactQuery hooks easier and clearer
English | 简体中文
Table of Contents
- Installation
- Examples
- Usage
- Issues
- LICENSE
Installation
This module is distributed via npm which is bundled with node and
should be installed as one of your project's dependencies
:
$ npm i react-query-kit
# or
$ yarn add react-query-kit
Examples
createQuery
Usage
import { QueryClient, dehydrate } from '@tanstack/react-query'
import { createQuery, inferData } from 'react-query-kit'
type Response = { title: string; content: string }
type Variables = { id: number }
const usePost = createQuery<Response, Variables, Error>({
primaryKey: '/posts',
queryFn: ({ queryKey: [primaryKey, variables] }) => {
// primaryKey equals to '/posts'
return fetch(`${primaryKey}/${variables.id}`).then(res => res.json())
},
// if u only wanna fetch once
enabled: (data) => !data,
suspense: true,
// u can also pass default options via useDefaultOptions
useDefaultOptions: () => {
const { id } = useSomething()
return {
variables: { id }
}
}
})
const variables = { id: 1 }
// example
export default function Page() {
// queryKey equals to ['/posts', { id: 1 }]
const { data } = usePost({ variables, suspense: true })
return (
<div>
<div>{data?.title}</div>
<div>{data?.content}</div>
</div>
)
}
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({
queryKey: usePost.getKey(variables),
queryFn: usePost.queryFn
})
return {
props: {
dehydratedState: dehydrate(queryClient),
},
}
}
// usage outside of react component
const data = await queryClient.fetchQuery({
queryKey: usePost.getKey(variables),
queryFn: usePost.queryFn
})
// useQueries example
const queries = useQueries({
queries: [
{ queryKey: usePost.getKey(variables), queryFn: usePost.queryFn },
{ queryKey: useProjects.getKey(), queryFn: useProjects.queryFn },
],
})
// setQueryData
queryClient.setQueryData<inferData<typeof usePost>>(usePost.getKey(variables), {...})
Additional API Reference
Options
primaryKey: string
- Required
primaryKey
will be the first element of the array ofqueryKey
enabled: boolean | ((data: TData, variables: TVariables) => boolean)
- Optional
- Set this to
false
to disable this query from automatically running. - If set to a function, the function will be executed with the latest data to compute the boolean
useDefaultOptions: () => QueryHookOptions
- Optional
- If u wanna inject return values from hooks into query, u can use this option.
Expose Methods
getPrimaryKey: () => primaryKey
getKey: (variables: TVariables) => [primaryKey, variables]
queryFn: QueryFunction<TFnData, [primaryKey, TVariables]>
queryKeyHashFn: (queryKey: [primaryKey, TVariables]) => string
Returns
queryKey: [primaryKey, TVariables]
- The query key of this custom query.
variables: TVariables
- The variables of this custom query.
setData: (updater: Updater<TData>, options?: SetDataOptions) => TData | undefined
- it's args similar with
queryClient.setQueryData
but withoutqueryKey
- it's args similar with
createInfiniteQuery
Usage
import { QueryClient, dehydrate } from '@tanstack/react-query'
import { createInfiniteQuery } from 'react-query-kit'
type Response = { projects: { id: string; name: string }[]; nextCursor: number }
type Variables = { active: boolean }
const useProjects = createInfiniteQuery<Response, Variables, Error>({
primaryKey: 'projects',
queryFn: ({ queryKey: [_primaryKey, variables], pageParam }) => {
return fetch(
`/projects?cursor=${pageParam}?active=${variables.active}`
).then(res => res.json())
},
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
defaultPageParam: 1,
})
const variables = { active: true }
// example
export default function Page() {
// queryKey equals to ['projects', { active: true }]
const { data, fetchNextPage, hasNextPage, isFetching, isFetchingNextPage } =
useProjects({ variables, suspense: true })
return (
<div>
{data.pages.map((group, i) => (
<React.Fragment key={i}>
{group.projects.map(project => (
<p key={project.id}>{project.name}</p>
))}
</React.Fragment>
))}
<div>
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage
? 'Loading more...'
: hasNextPage
? 'Load More'
: 'Nothing more to load'}
</button>
</div>
<div>{isFetching && !isFetchingNextPage ? 'Fetching...' : null}</div>
</div>
)
}
// nextjs example
export async function getStaticProps() {
const queryClient = new QueryClient()
await queryClient.prefetchInfiniteQuery({
queryKey: useProjects.getKey(variables),
queryFn: useProjects.queryFn,
})
return {
props: {
dehydratedState: dehydrate(queryClient),
},
}
}
// usage outside of react component
const data = await queryClient.fetchInfiniteQuery({
queryKey: useProjects.getKey(variables),
queryFn: useProjects.queryFn,
})
// usage outside of react component
const data = await queryClient.fetchInfiniteQuery({
queryKey: useProjects.getKey(variables),
queryFn: useProjects.queryFn,
})
Additional API Reference
Options
primaryKey: string
- Required
primaryKey
will be the first element of the arrary ofqueryKey
enabled: boolean | ((data: TData, variables: TVariables) => boolean)
- Optional
- Set this to
false
to disable this query from automatically running. - If set to a function, the function will be executed with the latest data to compute the boolean
useDefaultOptions: () => InfiniteQueryHookOptions
- Optional
- If u wanna inject return values from hooks into query, u can use this option.
Expose Methods
getPrimaryKey: () => primaryKey
getKey: (variables: TVariables) => [primaryKey, variables]
queryFn: QueryFunction<TFnData, [primaryKey, TVariables]>
queryKeyHashFn: (queryKey: [primaryKey, TVariables]) => string
Returns
queryKey: [primaryKey, TVariables]
- The query key of this custom query.
variables: TVariables
- The variables of this custom query.
setData: (updater: Updater<InfiniteData<TFnData>>, options?: SetDataOptions) => TData | undefined
- it's args similar with
queryClient.setQueryData
but withoutqueryKey
- it's args similar with
createMutation
Usage
import { createMutation } from 'react-query-kit'
const useAddTodo = createMutation({
mutationFn: async (variables: { title: string; content: string }) =>
fetch('/post', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
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 (
<div>
{mutation.isPending ? (
'Adding todo...'
) : (
<>
{mutation.isError ? (
<div>An error occurred: {mutation.error.message}</div>
) : null}
{mutation.isSuccess ? <div>Todo added!</div> : null}
<button
onClick={() => {
mutation.mutate({ title: 'Do Laundry', content: 'content...' })
}}
>
Create Todo
</button>
</>
)}
</div>
)
}
// usage outside of react component
useAddTodo.mutationFn({ title: 'Do Laundry', content: 'content...' })
Additional API Reference
Options
useDefaultOptions: () => MutationHookOptions
- Optional
- If u wanna inject return values from hooks into mutation, u can use this option.
Returns
getKey: () => MutationKey
mutationFn: MutationFunction<TData, TVariables>
Type inference
You can extract the TypeScript type of any custom hook with inferVariables
or inferData
import { inferVariables, inferData, inferFnData } from 'react-query-kit'
type Variables = inferVariables<typeof usePost>
type Data = inferData<typeof usePost>
type FnData = inferFnData<typeof usePost>
Caution
Since the variables
type of createQuery
or createInfiniteQuery
defaults to any
, the variables
option of a custom hook can pass any value when you don't set the type of varibables
, as below
const usePost = createQuery<Response>({...})
usePost({
// This will not throw type errors
variables: {foo: 1}
})
For stricter type validation, when you don't want to pass the variables
option, I suggest you use the void
type as the variables
type, as shown below. When you pass value to variables, usePost will throw a TypeError.
const usePost = createQuery<Response, void>({...})
usePost({
// This will throw a type error
variables: {foo: 1}
})
Issues
Looking to contribute? Look for the Good First Issue label.
🐛 Bugs
Please file an issue for bugs, missing documentation, or unexpected behavior.
💡 Feature Requests
Please file an issue to suggest new features. Vote on feature requests by adding a 👍. This helps maintainers prioritize what to work on.
LICENSE
MIT