Photo by PJH on Unsplash

On this page

This chapter follows GraphQL clients compared.

Install urql 5 with its peer graphql package:

Terminal window
pnpm add urql graphql
pnpm add @urql/exchange-auth @urql/exchange-retry @urql/exchange-graphcache

urql 5 builds on @urql/core 6. The old defaultExchanges export is gone—compose cacheExchange and fetchExchange explicitly (plus any optional exchanges).

Create the client

src/urql-client.ts
import { cacheExchange, createClient, fetchExchange } from "urql";
export const client = createClient({
url: import.meta.env.VITE_GRAPHQL_URL ?? "http://localhost:3000/graphql",
exchanges: [cacheExchange, fetchExchange],
fetchOptions: () => {
const token = localStorage.getItem("token");
return {
headers: token ? { Authorization: `Bearer ${token}` } : {},
};
},
});

Wrap the app with Provider from urql:

src/main.tsx
import { Provider } from "urql";
import { client } from "./urql-client";
root.render(
<Provider value={client}>
<App />
</Provider>,
);

Queries with useQuery

src/BooksList.tsx
import { gql, useQuery } from "urql";
const BooksQuery = gql`
query Books {
books {
id
title
}
}
`;
export function BooksList() {
const [{ data, fetching, error }] = useQuery({ query: BooksQuery });
if (fetching) return <p>Loading…</p>;
if (error) return <p>{error.message}</p>;
return (
<ul>
{data?.books.map((b) => (
<li key={b.id}>{b.title}</li>
))}
</ul>
);
}

Request policies and pause

Control caching behavior with requestPolicy:

Policy Behavior
cache-first Use cache if present, else network
cache-only Never hit network
network-only Skip cache read
cache-and-network Return cache, then refresh

Set pause: true when variables are incomplete so urql skips the request until ready.

Call reexecuteQuery from the hook result to refetch manually.

Mutations with useMutation

import { gql, useMutation } from "urql";
const CreateBook = gql`
mutation CreateBook($input: CreateBookInput!) {
createBook(input: $input) {
id
title
}
}
`;
export function AddBook() {
const [{ fetching }, execute] = useMutation(CreateBook);
return (
<button
disabled={fetching}
onClick={() => execute({ input: { title: "New" } })}
>
Add
</button>
);
}

The document cache updates for returned entities when IDs and __typename are present; complex graphs need Graphcache (next chapter).

Auth and retry exchanges

src/urql-client.ts
import { authExchange } from "@urql/exchange-auth";
import { retryExchange } from "@urql/exchange-retry";
createClient({
exchanges: [
cacheExchange,
authExchange(async (utils) => ({
addAuthToOperation(operation) {
const token = localStorage.getItem("token");
return token
? utils.appendHeaders(operation, { Authorization: `Bearer ${token}` })
: operation;
},
refreshAuth: async () => {
/* refresh token */
},
didAuthError: (err) => err.graphQLErrors.some((e) => e.extensions?.code === "UNAUTHENTICATED"),
})),
retryExchange({ maxNumberAttempts: 3 }),
fetchExchange,
],
url: "/graphql",
});

Order matters: auth and retry wrap fetch.

Subscriptions

Add subscriptionExchange with a graphql-ws client:

import { subscriptionExchange } from "urql";
import { createClient as createWsClient } from "graphql-ws";
const wsClient = createWsClient({ url: "ws://localhost:3000/graphql" });
subscriptionExchange({
forwardSubscription: (operation) => ({
subscribe: (sink) => ({
unsubscribe: wsClient.subscribe(
{ query: operation.query, variables: operation.variables },
sink,
),
}),
}),
});

Place it before fetchExchange in the exchange array.

Next: Typed operations with GraphQL Codegen.

Check your understanding

Question 1 of 6

0 of 6 checked

  1. 1. Which exchanges replace the removed defaultExchanges helper in urql 5?

Search articles

Type a word to search published articles.

Open the search page