
Security News
Feross on TBPN: How North Korea Hijacked Axios
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.
react-query-kit
Advanced tools
๐๏ธ A toolkit for ReactQuery that make ReactQuery hooks more reusable and typesafe
queryKey in a type-safe wayqueryClient's operations clearly associated with custom ReactQuery hooksEnglish | ็ฎไฝไธญๆ
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
If you still on React Query Kit v2? Check out the v2 docs here: https://github.com/liaoliao666/react-query-kit/tree/v2#readme.
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<Data> => {
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 (
<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(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: QueryFunctionContext<QueryKey, TPageParam>) => TFnData | Promise<TFnData>
QueryFunctionContext of queryFn.variables?: TVariables
variables will be the frist param of fetcher and the last element of the queryKey arrayuse: Middleware[]
Expose Methods
fetcher: (variables: TVariables, context: QueryFunctionContext<QueryKey, TPageParam>) => TFnData | Promise<TFnData>getKey: (variables: TVariables) => QueryKeygetOptions: (variables: TVariables) => UseQueryOptionsgetFetchOptions: (variables: TVariables) => ({ queryKey, queryFn, queryKeyHashFn })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<Data> => {
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 (
<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(
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: QueryFunctionContext<QueryKey, TPageParam>) => TFnData | Promise<TFnData>
QueryFunctionContext of queryFn.variables?: TVariables
variables will be the frist param of fetcher and the last element of the queryKey arrayuse: Middleware[]
Expose Methods
fetcher: (variables: TVariables, context: QueryFunctionContext<QueryKey, TPageParam>) => TFnData | Promise<TFnData>getKey: (variables: TVariables) => QueryKeygetOptions: (variables: TVariables) => UseInfiniteQueryOptionsgetFetchOptions: (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).
import { createSuspenseQuery } from 'react-query-kit'
createSuspenseQuery({
...options,
})
// equals to
createQuery({
...options,
enabled: true,
suspense: true,
throwOnError: true,
})
import { createSuspenseInfiniteQuery } from 'react-query-kit'
createSuspenseInfiniteQuery({
...options,
})
// equals to
createInfiniteQuery({
...options,
enabled: true,
suspense: true,
throwOnError: true,
})
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 (
<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...' })
Options
use: Middleware[]
Expose Methods
getKey: () => MutationKeygetOptions: () => UseMutationOptionsmutationFn: MutationFunction<TData, TVariables>router which allow you to create a shape of your entire API
import { router } from 'react-query-kit'
const post = router(`post`, {
byId: router.query({
fetcher: (variables: { id: number }) =>
fetch(`/posts/${variables.id}`).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<typeof post.list>
type FnData = inferFnData<typeof post.list>
type Variables = inferVariables<typeof post.list>
type Error = inferError<typeof post.list>
import { router } from 'react-query-kit'
const user = router(`user`, {})
const post = router(`post`, {})
const k = {
user,
post,
}
type Router = (key: string | unknown[], config: TConfig) => TRouter
Expose Methods
query
Similar to createQuery but without option queryKeyinfiniteQuery
Similar to createInfiniteQuery but without option queryKeymutation
Similar to createMutation but without option mutationKeyThis 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.
import { QueryClient } from '@tanstack/react-query'
import { Middleware, MutationHook, QueryHook, getKey } from 'react-query-kit'
const logger: Middleware<QueryHook<Data, Variables>> = useQueryNext => {
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<Data, Variables>({
use: [logger],
})
// global middlewares
const queryMiddleware: Middleware<QueryHook> = useQueryNext => {
return options => {
// u can also get queryKey via function getKey
const fullKey = getKey(options.queryKey, options.variables)
// ...
return useQueryNext(options)
}
}
const mutationMiddleware: Middleware<MutationHook> = useMutationNext => {
return options => {
// ...
return useMutationNext(options)
}
}
const queryClient = new QueryClient({
defaultOptions: {
queries: {
use: [queryMiddleware],
},
mutations: {
use: [mutationMiddleware],
},
},
})
Middleware will be merged from superior. For example:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
use: [a],
},
},
})
const useSomething = createQuery({
use: [b],
})
useSomething({ use: [c] })
is equivalent to:
createQuery({ use: [a, b, c] })
Each middleware wraps the next middleware, and the last one just wraps the useQuery. For example:
createQuery({ use: [a, b, c] })
The order of middleware executions will be a โ b โ c, as shown below:
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.
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.
type Data = { title: string; content: string }
type Variables = { id: number }
const usePost = createQuery({
queryKey: ['posts'],
fetcher: (variables: Variables): Promise<Data> => {
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.
type Data = { title: string; content: string }
type Variables = { id: number }
const usePost = createQuery<Data, Variables, Error>({
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 | null`
// `variables` 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
import { inferData, inferFnData, inferError, inferVariables, inferOptions } from 'react-query-kit'
const useProjects = createInfiniteQuery<Data, Variables, Error>(...)
inferData<typeof useProjects> // InfiniteData<Data>
inferFnData<typeof useProjects> // Data
inferVariables<typeof useProjects> // Variables
inferError<typeof useProjects> // Error
inferOptions<typeof useProjects> // InfiniteQueryHookOptions<...>
getFetchOptions and getOptions?getFetchOptions would only return necessary options, while options like staleTime and retry would be omited
fetcher and queryFn?ReactQueryKit would automatically converts fetcher to queryFn, as shown below:
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
createQuery({
- primaryKey: 'posts',
- queryFn: ({ queryKey: [_primaryKey, variables] }) => {},
+ queryKey: ['posts'],
+ fetcher: variables => {},
})
What you benefit from ReactQueryKit 3
Looking to contribute? Look for the Good First Issue label.
Please file an issue for bugs, missing documentation, or unexpected behavior.
Please file an issue to suggest new features. Vote on feature requests by adding a ๐. This helps maintainers prioritize what to work on.
MIT
FAQs
๐๏ธ A toolkit for ReactQuery that make ReactQuery hooks more reusable and typesafe
The npm package react-query-kit receives a total of 21,775 weekly downloads. As such, react-query-kit popularity was classified as popular.
We found that react-query-kit demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.ย It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.

Security News
OpenSSF has issued a high-severity advisory warning open source developers of an active Slack-based campaign using impersonation to deliver malware.

Research
/Security News
Malicious packages published to npm, PyPI, Go Modules, crates.io, and Packagist impersonate developer tooling to fetch staged malware, steal credentials and wallets, and enable remote access.