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
:
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.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.tsxtsx
functionMyComponent () {constutils =trpc .useContext ();utils .post .all .f ;// [...]}
MyComponent.tsxtsx
functionMyComponent () {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.
tsx
import { 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>);}
tsx
import { 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
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.tstsx
import { 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.tstsx
import { 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' }] };}),}),});
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 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️⃣!// [...]}
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 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:
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;}