Back to Trpc

useUtils

www/docs/client/react/useUtils.mdx

11.16.014.1 KB
Original Source

useUtils 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 useUtils helpers than what we provide here, we will link to their respective @tanstack/react-query docs so you can refer to them accordingly.

:::note

This hook was called useContext() until 10.41.0 (and is still aliased for the foreseeable future)

:::

Usage

useUtils 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:

twoslash
// @target: esnext

// @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;
ts
// @target: esnext
// @filename: server.ts
// ---cut---
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 useUtils gives us and reach the post.all query, we'll get access to our query helpers!

tsx
// @target: esnext
// @include: server
// @filename: MyComponent.tsx
// ---cut---
// @errors: 2339
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from './server';

const trpc = createTRPCReact<AppRouter>();

function MyComponent() {
  const utils = trpc.useUtils();
  utils.post.all.f;
  //              ^|
}

Helpers

These are the helpers you'll get access to via useUtils. 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
ensureDataqueryClient.ensureData
invalidatequeryClient.invalidateQueries
refetchqueryClient.refetchQueries
cancelqueryClient.cancelQueries
setDataqueryClient.setQueryData
setQueriesDataqueryClient.setQueriesData
getDataqueryClient.getQueryData
setInfiniteDataqueryClient.setInfiniteQueryData
getInfiniteDataqueryClient.getInfiniteData
setMutationDefaultsqueryClient.setMutationDefaults
getMutationDefaultsqueryClient.getMutationDefaults
isMutatingqueryClient.isMutating
resetqueryClient.resetQueries

❓ 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
// @jsx: react-jsx
// @filename: server.ts
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
export const appRouter = t.router({
  apiKey: t.router({
    create: t.procedure.mutation(() => 'new-api-key'),
  }),
});
export type AppRouter = typeof appRouter;

// @filename: utils/trpc.tsx
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../server';
export const trpc = createTRPCReact<AppRouter>();

// @filename: MyComponent.tsx
// ---cut---
import { useState } from 'react';
import { trpc } from './utils/trpc';

function MyComponent() {
  const [apiKey, setApiKey] = useState('');
  const utils = trpc.useUtils();

  return (
    <form
      onSubmit={async (event) => {
        const apiKey = await utils.client.apiKey.create.mutate();
        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
// @target: esnext
// @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' }] };
    }),
    byId: t.procedure
      .input(z.object({ id: z.number() }))
      .query(({ input }) => ({ post: { id: input.id, title: 'Look me up!' } })),
    edit: t.procedure
      .input(z.object({ id: z.number(), title: z.string() }))
      .mutation(({ input }) => ({ id: input.id, title: input.title })),
  }),
});
export type AppRouter = typeof appRouter;

// @filename: utils/trpc.tsx
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../server';
export const trpc = createTRPCReact<AppRouter>();

// @filename: MyComponent.tsx
// ---cut---
import { trpc } from './utils/trpc';

function MyComponent() {
  const utils = trpc.useUtils();

  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 than just one query.

Example code

<details> <summary>Backend code</summary>
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' }] };
    }),
  }),
});
</details>
tsx
// @target: esnext
// @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(() => ({
      posts: [{ id: 1, title: 'everlong' }, { id: 2, title: 'After Dark' }],
    })),
    byId: t.procedure
      .input(z.object({ id: z.number() }))
      .query(({ input }) => ({ post: { id: input.id, title: 'Look me up!' } })),
    edit: t.procedure
      .input(z.object({ id: z.number(), title: z.string() }))
      .mutation(({ input }) => ({ post: { id: input.id, title: input.title } })),
  }),
  user: t.router({
    all: t.procedure.query(() => ({
      users: [{ name: 'Dave Grohl' }, { name: 'Haruki Murakami' }],
    })),
  }),
});
export type AppRouter = typeof appRouter;

// @filename: utils/trpc.tsx
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../server';
export const trpc = createTRPCReact<AppRouter>();

// @filename: MyComponent.tsx
// ---cut---
import { trpc } from './utils/trpc';

function MyComponent() {
  const utils = trpc.useUtils();

  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.

We have added a feature to help with this:

ts
// @target: esnext
// @include: server
// @filename: utils/trpc.ts
// ---cut---
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../server';

export const trpc = createTRPCReact<AppRouter>({
  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 useUtils returns also contains the following properties:

ts
type AnyRouter = any;
type TRPCClient<T> = any;
type SSRState = false | 'prepass' | 'mounting' | 'mounted';
// ---cut---
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;
}