Skip to main content
Version: 10.x

useContext

useContext is a hook that gives you access to helpers that let you manage the cached data of the queries you execute via @trpc/react-query. These helpers are actually thin wrappers around @tanstack/react-query's queryClient methods. If you want more in-depth information about options and usage patterns for useContext helpers than what we provide here, we will link to their respective @tanstack/react-query docs so you can refer to them accordingly.

note

In v10, useContext no longer exposes the raw queryClient. If you need some of the methods that aren't wrapped by tRPC yet, import and use them directly from @tanstack/react-query:


tsx
import { useQueryClient } from '@tanstack/react-query';
function MyComponent() {
const queryClient = useQueryClient();
}
tsx
import { useQueryClient } from '@tanstack/react-query';
function MyComponent() {
const queryClient = useQueryClient();
}

Usage

useContext returns an object with all the available queries you have in your routers. You use it the same way as your trpc client object. Once you reach a query, you'll have access to the query helpers. For example, let's say you have a post router with an all query:

server.ts
ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
 
const t = initTRPC.create();
 
const appRouter = t.router({
post: t.router({
all: t.procedure.query(() => {
return {
posts: [
{ id: 1, title: 'everlong' },
{ id: 2, title: 'After Dark' },
],
};
}),
}),
});
 
export type AppRouter = typeof appRouter;
server.ts
ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
 
const t = initTRPC.create();
 
const appRouter = t.router({
post: t.router({
all: t.procedure.query(() => {
return {
posts: [
{ id: 1, title: 'everlong' },
{ id: 2, title: 'After Dark' },
],
};
}),
}),
});
 
export type AppRouter = typeof appRouter;

Now in our component, when we navigate the object useContext gives us and reach the post.all query, we'll get access to our query helpers!

MyComponent.tsx
tsx
function MyComponent() {
const utils = trpc.useContext();
utils.post.all.f;
                  
// [...]
}
MyComponent.tsx
tsx
function MyComponent() {
const utils = trpc.useContext();
utils.post.all.f;
                  
// [...]
}

Helpers

These are the helpers you'll get access to via useContext. The table below will help you know which tRPC helper wraps which @tanstack/react-query helper method. Each react-query method will link to its respective docs/guide:

tRPC helper wrapper@tanstack/react-query helper method
fetchqueryClient.fetchQuery
prefetchqueryClient.prefetchQuery
fetchInfinitequeryClient.fetchInfiniteQuery
prefetchInfinitequeryClient.prefetchInfiniteQuery
invalidatequeryClient.invalidateQueries
refetchqueryClient.refetchQueries
cancelqueryClient.cancelQuery
setDataqueryClient.setQueryData
getDataqueryClient.getQueryData
setInfiniteDataqueryClient.setInfiniteQueryData
getInfiniteDataqueryClient.getInfiniteData

❓ The function I want isn't here!

@tanstack/react-query has a lot of functions that we haven't put in the tRPC context yet. If you need a function that isn't here, feel free to open a feature request requesting it.

In the meantime, you can import and use the function directly from @tanstack/react-query. We provide a getQueryKey helper on routers and procedures so that you can easily provide the native function the correct query key.

tsx
function getQueryKey(
input: TInput,
type?: QueryType; /** @default 'any' */
)
type QueryType = "query" | "infinite" | "any";
// for useQuery ──┘ │ │
// for useInfinitQuery ─────┘ │
// will match any ───────────────────────┘
/** @note signature on router doesn't take any parameters */
tsx
function getQueryKey(
input: TInput,
type?: QueryType; /** @default 'any' */
)
type QueryType = "query" | "infinite" | "any";
// for useQuery ──┘ │ │
// for useInfinitQuery ─────┘ │
// will match any ───────────────────────┘
/** @note signature on router doesn't take any parameters */
tsx
import { useIsFetching, useQueryClient } from '@tanstack/react-query';
import { trpc } from '~/utils/trpc';
function MyComponent() {
const queryClient = useQueryClient();
const posts = trpc.post.list.useQuery();
// See if a query is fetching
const postListKey = trpc.post.list.getQueryKey(undefined, 'infinite');
const isFetching = useIsFetching(postListKey);
// Set some query defaults for an entire router
const postKey = trpc.post.getQueryKey(undefined, 'any');
queryClient.setQueryDefaults(postKey, { staleTime: 30 * 60 * 1000 });
// ...
}
tsx
import { useIsFetching, useQueryClient } from '@tanstack/react-query';
import { trpc } from '~/utils/trpc';
function MyComponent() {
const queryClient = useQueryClient();
const posts = trpc.post.list.useQuery();
// See if a query is fetching
const postListKey = trpc.post.list.getQueryKey(undefined, 'infinite');
const isFetching = useIsFetching(postListKey);
// Set some query defaults for an entire router
const postKey = trpc.post.getQueryKey(undefined, 'any');
queryClient.setQueryDefaults(postKey, { staleTime: 30 * 60 * 1000 });
// ...
}

Proxy client

In addition to the above react-query helpers, the context also exposes your tRPC proxy client. This lets you call your procedures with async/await without needing to create an additional vanilla client.

tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const [apiKey, setApiKey] = useState();
const utils = trpc.useContext();
return (
<Form
handleSubmit={async (event) => {
const apiKey = await utils.client.apiKey.create.mutate(event);
setApiKey(apiKey);
}}
>
...
</Form>
);
}
tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const [apiKey, setApiKey] = useState();
const utils = trpc.useContext();
return (
<Form
handleSubmit={async (event) => {
const apiKey = await utils.client.apiKey.create.mutate(event);
setApiKey(apiKey);
}}
>
...
</Form>
);
}

Query Invalidation

You invalidate queries via the invalidate helper. invalidate is actually a special helper given that, unlike the other helpers, it's available at every level of the router map. This means you can either run invalidate on a single query, a whole router, or every router if you want. We get more in detail in the sections below.

Invalidating a single query

You can invalidate a query relating to a single procedure and even filter based on the input passed to it to prevent unnecessary calls to the back end.

Example code

tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const utils = trpc.useContext();
const mutation = trpc.post.edit.useMutation({
onSuccess(input) {
utils.post.all.invalidate();
utils.post.byId.invalidate({ id: input.id }); // Will not invalidate queries for other id's 👍
},
});
// [...]
}
tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const utils = trpc.useContext();
const mutation = trpc.post.edit.useMutation({
onSuccess(input) {
utils.post.all.invalidate();
utils.post.byId.invalidate({ id: input.id }); // Will not invalidate queries for other id's 👍
},
});
// [...]
}

Invalidating across whole routers

It is also possible to invalidate queries across an entire router rather then just one query.

Example code

Backend code
server/routers/_app.ts
tsx
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
export const t = initTRPC.create();
export const appRouter = t.router({
// sub Post router
post: t.router({
all: t.procedure.query(() => {
return {
posts: [
{ id: 1, title: 'everlong' },
{ id: 2, title: 'After Dark' },
],
};
}),
byId: t.procedure
.input(
z.object({
id: z.string(),
}),
)
.query(({ input }) => {
return {
post: { id: input?.id, title: 'Look me up!' },
};
}),
edit: t.procedure
.input(z.object({ id: z.number(), title: z.string() }))
.mutation(({ input }) => {
return { post: { id: input.id, title: input.title } };
}),
}),
// separate user router
user: t.router({
all: t.procedure.query(() => {
return { users: [{ name: 'Dave Grohl' }, { name: 'Haruki Murakami' }] };
}),
}),
});
server/routers/_app.ts
tsx
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
export const t = initTRPC.create();
export const appRouter = t.router({
// sub Post router
post: t.router({
all: t.procedure.query(() => {
return {
posts: [
{ id: 1, title: 'everlong' },
{ id: 2, title: 'After Dark' },
],
};
}),
byId: t.procedure
.input(
z.object({
id: z.string(),
}),
)
.query(({ input }) => {
return {
post: { id: input?.id, title: 'Look me up!' },
};
}),
edit: t.procedure
.input(z.object({ id: z.number(), title: z.string() }))
.mutation(({ input }) => {
return { post: { id: input.id, title: input.title } };
}),
}),
// separate user router
user: t.router({
all: t.procedure.query(() => {
return { users: [{ name: 'Dave Grohl' }, { name: 'Haruki Murakami' }] };
}),
}),
});
tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const utils = trpc.useContext();
const invalidateAllQueriesAcrossAllRouters = () => {
// 1️⃣
// All queries on all routers will be invalidated 🔥
utils.invalidate();
};
const invalidateAllPostQueries = () => {
// 2️⃣
// All post queries will be invalidated 📭
utils.post.invalidate();
};
const invalidatePostById = () => {
// 3️⃣
// All queries in the post router with input {id:1} invalidated 📭
utils.post.byId.invalidate({ id: 1 });
};
// Example queries
trpc.user.all.useQuery(); // Would only be validated by 1️⃣ only.
trpc.post.all.useQuery(); // Would be invalidated by 1️⃣ & 2️⃣
trpc.post.byId.useQuery({ id: 1 }); // Would be invalidated by 1️⃣, 2️⃣ and 3️⃣
trpc.post.byId.useQuery({ id: 2 }); // would be invalidated by 1️⃣ and 2️⃣ but NOT 3️⃣!
// [...]
}
tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const utils = trpc.useContext();
const invalidateAllQueriesAcrossAllRouters = () => {
// 1️⃣
// All queries on all routers will be invalidated 🔥
utils.invalidate();
};
const invalidateAllPostQueries = () => {
// 2️⃣
// All post queries will be invalidated 📭
utils.post.invalidate();
};
const invalidatePostById = () => {
// 3️⃣
// All queries in the post router with input {id:1} invalidated 📭
utils.post.byId.invalidate({ id: 1 });
};
// Example queries
trpc.user.all.useQuery(); // Would only be validated by 1️⃣ only.
trpc.post.all.useQuery(); // Would be invalidated by 1️⃣ & 2️⃣
trpc.post.byId.useQuery({ id: 1 }); // Would be invalidated by 1️⃣, 2️⃣ and 3️⃣
trpc.post.byId.useQuery({ id: 2 }); // would be invalidated by 1️⃣ and 2️⃣ but NOT 3️⃣!
// [...]
}

Invalidate full cache on every mutation

Keeping track of exactly what queries a mutation should invalidate is hard, therefore, it can be a pragmatic solution to invalidate the full cache as a side-effect on any mutation. Since we have request batching, this invalidation will simply refetch all queries on the page you're looking at in one single request.

info

We have marked this API as unstable_ as the exact API might change without a major version bump on tRPC, keep an eye on the release notes if you want to use this.

We have added a feature to help with this:

ts
export const trpc = createTRPCReact<AppRouter, SSRContext>({
unstable_overrides: {
useMutation: {
/**
* This function is called whenever a `.useMutation` succeeds
**/
async onSuccess(opts) {
/**
* @note that order here matters:
* The order here allows route changes in `onSuccess` without
* having a flash of content change whilst redirecting.
**/
// Calls the `onSuccess` defined in the `useQuery()`-options:
await opts.originalFn();
// Invalidate all queries in the react-query cache:
await opts.queryClient.invalidateQueries();
},
},
},
});
ts
export const trpc = createTRPCReact<AppRouter, SSRContext>({
unstable_overrides: {
useMutation: {
/**
* This function is called whenever a `.useMutation` succeeds
**/
async onSuccess(opts) {
/**
* @note that order here matters:
* The order here allows route changes in `onSuccess` without
* having a flash of content change whilst redirecting.
**/
// Calls the `onSuccess` defined in the `useQuery()`-options:
await opts.originalFn();
// Invalidate all queries in the react-query cache:
await opts.queryClient.invalidateQueries();
},
},
},
});

Additional Options

Aside from the query helpers, the object useContext returns also contains the following properties:

ts
interface ProxyTRPCContextProps<TRouter extends AnyRouter, TSSRContext> {
/**
* The `TRPCClient`
*/
client: TRPCClient<TRouter>;
/**
* The SSR context when server-side rendering
* @default null
*/
ssrContext?: TSSRContext | null;
/**
* State of SSR hydration.
* - `false` if not using SSR.
* - `prepass` when doing a prepass to fetch queries' data
* - `mounting` before TRPCProvider has been rendered on the client
* - `mounted` when the TRPCProvider has been rendered on the client
* @default false
*/
ssrState?: SSRState;
/**
* Abort loading query calls when unmounting a component - usually when navigating to a new page
* @default false
*/
abortOnUnmount?: boolean;
}
ts
interface ProxyTRPCContextProps<TRouter extends AnyRouter, TSSRContext> {
/**
* The `TRPCClient`
*/
client: TRPCClient<TRouter>;
/**
* The SSR context when server-side rendering
* @default null
*/
ssrContext?: TSSRContext | null;
/**
* State of SSR hydration.
* - `false` if not using SSR.
* - `prepass` when doing a prepass to fetch queries' data
* - `mounting` before TRPCProvider has been rendered on the client
* - `mounted` when the TRPCProvider has been rendered on the client
* @default false
*/
ssrState?: SSRState;
/**
* Abort loading query calls when unmounting a component - usually when navigating to a new page
* @default false
*/
abortOnUnmount?: boolean;
}