Skip to content

Getting started ​

Install ​

sh
npm i normalized-react-query @tanstack/react-query
sh
pnpm add normalized-react-query @tanstack/react-query
sh
yarn add normalized-react-query @tanstack/react-query

TIP

@tanstack/react-query (v5) is a peer dependency, and does all the caching, refetching and subscribing. normalized-react-query is ESM only, and requires Node.js 24 or later for server code.

Define a resource ​

A resource pairs a query key with the function that loads it. Define it once, in a module of its own, and import it wherever the data is used:

ts
import { 
resource
} from 'normalized-react-query';
export const
users
=
resource
({
key
: ({
id
}: {
id
: number }) => ['users',
id
],
queryFn
: ({
params
}) =>
getUser
(
params
.
id
),
});
  • key computes the key from the params. A resource that takes no params can use a fixed key: key: ['settings'].
  • queryFn receives the params, as params, along with TanStack's usual context (signal, queryKey, …).
  • The params and data types come from these two functions. You can also give them explicitly: resource<{ id: number }, User>({ ... }).

Use it in a component ​

Pass the resource and its params to a hook. The key and fetcher come from the resource, and the result is typed:

ts
import { 
skipToken
} from '@tanstack/react-query';
import {
useNormalizedQuery
,
useNormalizedSuspenseQuery
} from 'normalized-react-query';
export const
useUserName
= (
id
: number | null) => {
// `skipToken` disables the query, as in TanStack const {
data
} =
useNormalizedQuery
(
users
,
id
=== null ?
skipToken
: {
id
});
const data: User | undefined

The last successfully resolved data for the query.

return
data
?.
name
;
}; export const
useUser
= (
id
: number) =>
useNormalizedSuspenseQuery
(
users
, {
id
}).
data
;

Each hook takes the options of its TanStack counterpart (staleTime, select, enabled, …), except queryKey and queryFn, and optionally a QueryClient as its last argument. See the hooks reference.

Use the cache ​

Every cache operation takes the QueryClient, then the params, and is typed to the resource's data:

ts
import type { 
QueryClient
} from '@tanstack/react-query';
export const
rename
= async (
client
:
QueryClient
,
id
: number,
name
: string) => {
users
.
setQueryData
(
client
, {
id
},
user
=>
user
&& { ...
user
,
name
});
await
users
.
invalidateQuery
(
client
, {
id
});
}; export const
loadUser
= async (
client
:
QueryClient
,
id
: number) =>
users
.
query
(
client
, {
id
});

Next steps ​