Making GraphQL Requests Easy With React TypeScript And React Query

Learn how to build type-safe, performant GraphQL applications using React Query and GraphQL Code Generator. A modern alternative to Apollo Client that gives you flexibility without sacrificing type safety.

Why Combine GraphQL with React Query

React has become the dominant framework for building modern web applications, and when it comes to data fetching, developers have numerous options. While Apollo Client has long been the default choice for GraphQL in React, an increasing number of teams are discovering that combining GraphQL with TanStack Query (formerly React Query) offers a more flexible, lightweight, and type-safe approach to managing server state. This guide explores how to make GraphQL requests simple and type-safe using React Query and GraphQL Code Generator, providing a modern alternative to traditional GraphQL client libraries.

The combination of GraphQL's precise data fetching capabilities with React Query's powerful caching and synchronization features creates a development experience that reduces boilerplate while maintaining complete type safety throughout your application. By leveraging code generation, you can eliminate the manual type definitions that often become outdated as your schema evolves, ensuring that your frontend always accurately reflects the capabilities of your backend API.

As noted in the TanStack Query comparison guide, React Query was designed as a data-fetching and caching library that works with any asynchronous data source, including GraphQL APIs. Unlike Apollo Client, which is tightly coupled to GraphQL concepts, React Query provides a lightweight abstraction layer that handles caching, background updates, and refetching with minimal configuration.

For teams building web applications with React, this combination of GraphQL and React Query provides a robust foundation for managing server state efficiently.

Key Benefits of React Query with GraphQL

Lightweight Architecture

Unlike Apollo Client, React Query provides a lightweight abstraction layer that handles caching, background updates, and refetching with minimal configuration.

Type Safety

GraphQL Code Generator analyzes your schema and operations to automatically generate TypeScript types and React Query hooks, ensuring end-to-end type safety.

Flexible Integration

React Query works with any GraphQL client or fetch mechanism you prefer, giving you the freedom to choose your architecture.

Built-in Caching

Stale-while-revalidate patterns, background refetching, and window focus refetching come built-in, keeping your application synchronized with the server.

Setting Up GraphQL Code Generator

GraphQL Code Generator is the key to achieving end-to-end type safety when using React Query with GraphQL. The tool analyzes your GraphQL schema and your client-side operations to automatically generate TypeScript types and React Query hooks, ensuring that your frontend code always matches your backend schema.

According to The Guild's official documentation, the code generator analyzes your GraphQL schema and client-side operations to create type-safe hooks that integrate seamlessly with React Query's APIs.

Implementing type-safe data fetching is a core practice in modern web development services, helping teams deliver more reliable applications faster.

codegen.ts
1import type { CodegenConfig } from '@graphql-codegen/cli';2 3const config: CodegenConfig = {4 schema: 'https://your-graphql-endpoint.com/graphql',5 documents: ['src/**/*.{ts,tsx}'],6 ignoreNoDocuments: true,7 generates: {8 './src/graphql/': {9 preset: 'client',10 config: {11 documentMode: 'string'12 }13 },14 './schema.graphql': {15 plugins: ['schema-ast'],16 config: {17 includeDirectives: true18 }19 }20 }21};22 23export default config;

The configuration uses the client preset, which is the recommended approach for generating React Query hooks. This preset creates hooks that integrate seamlessly with React Query's useQuery and useMutation APIs while providing complete type inference for your operations. The client preset generates hooks that wrap React Query's standard hooks, adding type safety without sacrificing flexibility.

tsconfig.json
1{2 "compilerOptions": {3 "plugins": [4 {5 "name": "@0no-co/graphqlsp",6 "schema": "./schema.graphql"7 }8 ]9 }10}

Update your tsconfig.json to include the GraphQL language server plugin. This integration provides autocomplete for GraphQL fields and arguments directly in your TypeScript files, catching schema mismatches before you even run your code. As documented by The Guild, this LSP integration is essential for a productive developer experience when working with GraphQL and TypeScript.

Writing Type-Safe GraphQL Operations

With GraphQL Code Generator configured, you can now write GraphQL operations in your React components with complete type safety. The generator creates a graphql function that you import from the generated folder, which provides typed hooks for each operation you define.

When you run the code generator, it creates hooks like useGetUsersQuery and useGetUsersLazyQuery that wrap React Query's useQuery and useLazyQuery with full type inference. The generated hooks automatically include the correct query key structure, variables types, and return types based on your schema and operation definitions.

The type inference extends to every level of your response data. If your GraphQL schema defines a user with a name field of type String, your component will know that the returned data contains a string, not just any or unknown. This eliminates an entire category of runtime errors where components attempt to access fields that don't exist or have the wrong type. As highlighted in LogRocket's tutorial, this type safety significantly reduces bugs and improves developer confidence.

This level of type safety is particularly valuable for teams working on enterprise web applications where reliability and maintainability are critical.

GetUsers.ts
1import { graphql } from '../graphql';2 3const GetUsersDocument = graphql(`4 query GetUsers {5 users {6 id7 name8 email9 avatarUrl10 }11 }12`);13 14// Generated hook provides:15// - useGetUsersQuery(queryClient, variables, options)16// - useGetUsersLazyQuery()17// - Full type inference for data and variables18// - Type-safe error handling

Integrating Generated Hooks with React Query

React Query provides the data fetching infrastructure, while the generated hooks from GraphQL Code Generator bridge the gap between GraphQL operations and React Query's APIs. This integration gives you the caching, background updates, and optimistic updates that React Query is known for, combined with the type safety that GraphQL enables.

The generated useQuery hook accepts the same options as React Query's standard hook, allowing you to configure caching behavior, refetch intervals, and error handling. According to the TanStack Query documentation, this separation of concerns makes debugging easier and provides more flexibility in how you manage server state.

For applications requiring advanced capabilities like AI-powered features, this architecture provides the flexibility to integrate additional data sources while maintaining clean, type-safe code.

UsersList.tsx
1import { useGetUsersQuery } from '../graphql/generated';2 3function UsersList() {4 const { data, isLoading, error, refetch } = useGetUsersQuery(5 queryClient,6 {},7 {8 staleTime: 5 * 60 * 1000,9 refetchOnWindowFocus: false,10 }11 );12 13 if (isLoading) return <LoadingSpinner />;14 if (error) return <ErrorMessage error={error} />;15 16 return (17 <ul>18 {data?.users.map(user => (19 <li key={user.id}>{user.name}</li>20 ))}21 </ul>22 );23}

The generated hooks return the same structure as React Query's standard hooks: data, isLoading, isError, error, isFetching, and refetch. This means you can use React Query's DevTools and standard patterns without modification. The LogRocket guide provides additional patterns for dependent queries and more complex scenarios.

Optimistic Updates and Cache Management

React Query's cache manipulation capabilities become even more powerful when combined with type-safe GraphQL mutations. When performing optimistic updates, you can safely modify cached data knowing that your types accurately reflect what's in the cache.

The type-safe nature of the generated hooks means that when you update the cache, TypeScript validates that your updates match the expected data structure. This catches mistakes early and provides confidence when modifying cached GraphQL responses. Invalidation queries also benefit from type safety--when you invalidate a query key, React Query knows exactly what data to refetch, and the generated hooks ensure that the refetched data matches your expected types.

As documented in the TanStack Query guides, this combination of type safety and cache manipulation provides a robust foundation for building responsive user interfaces.

Effective cache management is essential for high-performance web applications that need to deliver snappy user experiences.

optimistic-update.ts
1const { mutateAsync: updateTodo } = useUpdateTodoMutation(queryClient, {2 onMutate: async (newTodo) => {3 await queryClient.cancelQueries({ queryKey: ['todos'] });4 const previousTodos = queryClient.getQueryData(['todos']);5 6 queryClient.setQueryData(['todos'], (old) =>7 old.map(todo =>8 todo.id === newTodo.id ? { ...todo, ...newTodo } : todo9 )10 );11 12 return { previousTodos };13 },14 onError: (err, newTodo, context) => {15 queryClient.setQueryData(['todos'], context.previousTodos);16 },17 onSettled: () => {18 queryClient.invalidateQueries({ queryKey: ['todos'] });19 }20});

Performance Considerations

Using React Query with GraphQL offers several performance advantages over traditional GraphQL clients. Apollo Client's normalized cache, while powerful, can introduce significant memory overhead for applications with complex object graphs. React Query's caching, by contrast, stores responses as-is without normalization, which is more efficient for many use cases.

React Query's stale-while-revalidate pattern provides immediate data display while fetching fresh results in the background. This creates a perceived performance improvement for users, as they see content instantly rather than waiting for loading states. The combination of optimistic UI updates and background refetching means users rarely experience loading states for data that hasn't changed.

According to the TanStack Query comparison, React Query handles all the complexity of determining when to refetch, how long to keep data cached, and how to prioritize concurrent requests--without the overhead of a normalized cache.

cache-optimization.tsx
1const { data, isFetching } = useGetPostsQuery(queryClient, {}, {2 staleTime: 60 * 1000, // Data is fresh for 1 minute3 gcTime: 30 * 60 * 1000, // Keep in cache for 30 minutes4});5 6return (7 <div>8 {isFetching && <LoadingIndicator />}9 <PostList posts={data?.posts} />10 </div>11);

The combination of optimistic UI updates and background refetching means users rarely experience loading states for data that hasn't changed. React Query handles all the complexity of determining when to refetch, how long to keep data cached, and how to prioritize concurrent requests.

React Query vs Apollo Client

While Apollo Client has been the dominant GraphQL client for React applications, the combination of React Query with GraphQL Code Generator offers several advantages that make it an increasingly popular choice.

Apollo Client requires learning its specific caching mechanism, cache update patterns, and query management API. React Query's useQuery and useMutation hooks, by contrast, follow patterns that are familiar to any React developer and work consistently across different data sources. This reduces onboarding time for new team members and makes code reviews more straightforward.

Bundle size is another consideration. Apollo Client's comprehensive feature set comes with a significant bundle size contribution. React Query provides a more lightweight solution while still offering powerful caching capabilities. For applications that don't need Apollo's normalized caching, this can result in meaningful performance improvements.

Feature comparison between React Query and Apollo Client
FeatureReact QueryApollo Client
Bundle SizeLightweight (~3KB)Larger (~40KB)
Learning CurveFamiliar React patternsGraphQL-specific concepts
Caching StrategySimple, non-normalizedNormalized cache with ID-based deduplication
Type SafetyVia GraphQL Code GeneratorVia code generation or Apollo CLI
FlexibilityWorks with any fetch mechanismTightly coupled to GraphQL

Error Handling Strategies

Proper error handling is crucial for any data fetching strategy, and the combination of React Query with GraphQL provides multiple approaches. The generated hooks include typed error states that match your GraphQL error structure.

GraphQL errors include extensions that can contain error codes, which is useful for displaying user-friendly messages or handling specific error types differently. The type-safe error structure means you can destructure error properties with confidence. React Query also supports retry mechanisms for failed queries, with configurable backoff strategies.

As described in the LogRocket tutorial, this typed error handling makes it easier to provide meaningful feedback to users when something goes wrong.

error-handling.tsx
1const { data, error, isError } = useGetUserQuery(queryClient, { id: userId });2 3if (isError) {4 return (5 <ErrorDisplay6 message={error.message}7 extensions={error.extensions}8 code={error.extensions?.code}9 />10 );11}12 13// With retry configuration14const { data } = useGetDataQuery(queryClient, {}, {15 retry: 3,16 retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),17});

Best Practices and Recommendations

When adopting React Query with GraphQL Code Generator, several best practices will help you maximize type safety and developer productivity. First, run the code generator in watch mode during development to get instant feedback on type changes. This automatically regenerates types whenever your schema or operations change, catching type mismatches immediately.

Second, leverage the TypeScript LSP integration for autocomplete while writing GraphQL operations. This catches schema errors before you even save your files and helps discover available fields without consulting documentation. Third, organize your GraphQL operations colocated with the components that use them. GraphQL Code Generator's client preset supports this pattern, generating hooks that can be imported from a centralized location.

Finally, use React Query's built-in DevTools during development to visualize cache state, track queries, and debug issues. The DevTools provide invaluable insights into what's happening with your queries, helping you optimize caching strategies and identify issues early in development.

terminal
1# Run codegen in watch mode2npx graphql-codegen --config codegen.ts --watch
App.tsx
1import { ReactQueryDevtools } from '@tanstack/react-query-devtools';2 3function App() {4 return (5 <QueryClientProvider client={queryClient}>6 <YourApp />7 <ReactQueryDevtools initialIsOpen={false} />8 </QueryClientProvider>9 );10}

The DevTools provide invaluable insights into what's happening with your queries, helping you optimize caching strategies and identify issues early in development. According to TanStack Query documentation, the DevTools are essential for understanding cache behavior and debugging data fetching issues.

Common Questions

Conclusion

Combining React Query with GraphQL and GraphQL Code Generator provides a powerful, type-safe approach to data fetching in React applications. This combination gives you the precise data fetching that GraphQL enables, the caching and synchronization that React Query provides, and the end-to-end type safety that code generation ensures.

As your application grows, this foundation scales gracefully, maintaining type safety across your entire codebase while providing the caching and performance optimizations that production applications require. The approach works particularly well for teams that value simplicity and flexibility over the opinionated patterns of dedicated GraphQL clients.

By separating concerns between data fetching (React Query) and type generation (GraphQL Code Generator), you gain the ability to swap implementations if needed while still benefiting from strong typing throughout your application. Whether you're starting a new project or considering a migration from Apollo Client, this combination deserves serious consideration for modern React applications.

Our web development team has extensive experience implementing type-safe data fetching architectures that scale. Contact us to learn how we can help you build more reliable React applications with GraphQL and React Query.

Ready to Modernize Your Data Fetching?

At Digital Thrive, we specialize in building high-performance React applications using modern tools like React Query and GraphQL. Our team can help you implement type-safe data fetching strategies that improve both developer experience and application performance.

Sources

  1. The Guild: GraphQL Codegen with React Query - Official documentation on setting up GraphQL Code Generator with React Query
  2. TanStack Query: Comparison Guide - Official comparison of data fetching libraries
  3. LogRocket: Making GraphQL Requests Easy with React Query - Practical tutorial on type-safe GraphQL with React Query