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.
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:
tsximport { useQueryClient } from '@tanstack/react-query';function MyComponent() {const queryClient = useQueryClient();}
tsximport { 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.tsts// @filename: server.tsimport {initTRPC } from '@trpc/server';import {z } from 'zod';constt =initTRPC .create ();constappRouter =t .router ({post :t .router ({all :t .procedure .query (() => {return {posts : [{id : 1,title : 'everlong' },{id : 2,title : 'After Dark' },],};}),}),});export typeAppRouter = typeofappRouter ;
server.tsts// @filename: server.tsimport {initTRPC } from '@trpc/server';import {z } from 'zod';constt =initTRPC .create ();constappRouter =t .router ({post :t .router ({all :t .procedure .query (() => {return {posts : [{id : 1,title : 'everlong' },{id : 2,title : 'After Dark' },],};}),}),});export typeAppRouter = typeofappRouter ;
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.tsxtsxfunctionMyComponent () {constutils =trpc .useContext ();utils .post .all .f ;// [...]}
MyComponent.tsxtsxfunctionMyComponent () {constutils =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 |
|---|---|
fetch | queryClient.fetchQuery |
prefetch | queryClient.prefetchQuery |
fetchInfinite | queryClient.fetchInfiniteQuery |
prefetchInfinite | queryClient.prefetchInfiniteQuery |
invalidate | queryClient.invalidateQueries |
refetch | queryClient.refetchQueries |
cancel | queryClient.cancelQuery |
setData | queryClient.setQueryData |
getData | queryClient.getQueryData |
setInfiniteData | queryClient.setInfiniteQueryData |
getInfiniteData | queryClient.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 also provide a getQueryKey which you can use to get the correct queryKey on the filters when using these functions.
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.
tsximport { trpc } from '../utils/trpc';function MyComponent() {const [apiKey, setApiKey] = useState();const utils = trpc.useContext();return (<FormhandleSubmit={async (event) => {const apiKey = await utils.client.apiKey.create.mutate(event);setApiKey(apiKey);}}>...</Form>);}
tsximport { trpc } from '../utils/trpc';function MyComponent() {const [apiKey, setApiKey] = useState();const utils = trpc.useContext();return (<FormhandleSubmit={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
tsximport { 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 👍},});// [...]}
tsximport { 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.tstsximport { initTRPC } from '@trpc/server';import { z } from 'zod';export const t = initTRPC.create();export const appRouter = t.router({// sub Post routerpost: 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 routeruser: t.router({all: t.procedure.query(() => {return { users: [{ name: 'Dave Grohl' }, { name: 'Haruki Murakami' }] };}),}),});
server/routers/_app.tstsximport { initTRPC } from '@trpc/server';import { z } from 'zod';export const t = initTRPC.create();export const appRouter = t.router({// sub Post routerpost: 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 routeruser: t.router({all: t.procedure.query(() => {return { users: [{ name: 'Dave Grohl' }, { name: 'Haruki Murakami' }] };}),}),});
tsximport { 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 queriestrpc.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️⃣!// [...]}
tsximport { 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 queriestrpc.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
We have prefixed this as unstable_ as it's a new API, but you're safe to use it! Read more.
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.
We have added a feature to help with this:
tsexport 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();},},},});
tsexport 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:
tsinterface 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;}
tsinterface 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;}