Using React Query with GraphQL for Optimized Fetching
Introduction
Combine the power of React Query’s caching with GraphQL’s precise data fetching for optimal user experience and performance.
Prerequisites
- React project setup
- GraphQL endpoint available
Step 1: Install Dependencies
npm install @tanstack/react-query graphql-request graphql
Step 2: Configure React Query Client
Create lib/queryClient.ts
:
import { QueryClient } from "@tanstack/react-query";
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
retry: 3,
},
},
});
Step 3: Create GraphQL Client
Create lib/graphql.ts
:
import { GraphQLClient } from "graphql-request";
const endpoint =
process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT || "http://localhost:4000/graphql";
export const graphqlClient = new GraphQLClient(endpoint, {
headers: {
authorization: `Bearer ${typeof window !== "undefined" ? localStorage.getItem("token") : ""}`,
},
});
Step 4: Define GraphQL Queries
Create queries/users.ts
:
import { gql } from "graphql-request";
export const GET_USERS = gql`
query GetUsers($limit: Int, $offset: Int) {
users(limit: $limit, offset: $offset) {
id
name
email
avatar
}
}
`;
export const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
avatar
posts {
id
title
content
}
}
}
`;
export const CREATE_USER = gql`
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
`;
Step 5: Create Custom Hooks
Create hooks/useUsers.ts
:
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { graphqlClient } from "@/lib/graphql";
import { GET_USERS, GET_USER, CREATE_USER } from "@/queries/users";
export function useUsers(limit = 10, offset = 0) {
return useQuery({
queryKey: ["users", { limit, offset }],
queryFn: () => graphqlClient.request(GET_USERS, { limit, offset }),
});
}
export function useUser(id: string) {
return useQuery({
queryKey: ["user", id],
queryFn: () => graphqlClient.request(GET_USER, { id }),
enabled: !!id,
});
}
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (input: any) => graphqlClient.request(CREATE_USER, { input }),
onSuccess: () => {
// Invalidate and refetch users list
queryClient.invalidateQueries({ queryKey: ["users"] });
},
});
}
Step 6: Setup Provider
Update app/layout.tsx
:
'use client'
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { queryClient } from '@/lib/queryClient';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</body>
</html>
);
}
Step 7: Use in Components
Create components/UsersList.tsx
:
import { useUsers, useCreateUser } from '@/hooks/useUsers';
import { useState } from 'react';
export default function UsersList() {
const [page, setPage] = useState(0);
const { data, isLoading, error } = useUsers(10, page * 10);
const createUser = useCreateUser();
const handleCreateUser = () => {
createUser.mutate({
name: 'New User',
email: 'newuser@example.com',
});
};
if (isLoading) return <div>Loading users...</div>;
if (error) return <div>Error loading users</div>;
return (
<div>
<button
onClick={handleCreateUser}
disabled={createUser.isPending}
className="mb-4 px-4 py-2 bg-blue-600 text-white rounded"
>
{createUser.isPending ? 'Creating...' : 'Create User'}
</button>
<div className="grid gap-4">
{data?.users?.map((user: any) => (
<div key={user.id} className="p-4 border rounded">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
))}
</div>
<div className="mt-4 space-x-2">
<button
onClick={() => setPage(Math.max(0, page - 1))}
disabled={page === 0}
className="px-4 py-2 bg-gray-200 rounded"
>
Previous
</button>
<button
onClick={() => setPage(page + 1)}
className="px-4 py-2 bg-gray-200 rounded"
>
Next
</button>
</div>
</div>
);
}
Summary
React Query with GraphQL provides powerful caching, background updates, and optimistic UI patterns while maintaining GraphQL’s flexible data fetching capabilities.