React Native and GraphQL in 2026: Apollo Client, Queries and Interview Questions

Learn to integrate GraphQL with React Native using Apollo Client 4.0. This tutorial covers queries, mutations, caching strategies, and common interview questions about mobile GraphQL development.

React Native and GraphQL integration with Apollo Client code architecture

GraphQL has become the preferred API layer for React Native applications that need flexible data fetching and efficient network usage. Apollo Client 4.0, released in late 2025, brings a leaner architecture with 20-30% smaller bundles and improved TypeScript support specifically designed for mobile environments.

Apollo Client 4.0 Key Changes

Apollo Client 4.0 decouples React-specific exports to @apollo/client/react, supports React 19 Suspense and the React Compiler, and replaces the monolithic ApolloError with specific error classes for better debugging.

Setting Up Apollo Client 4 in React Native

The installation process remains straightforward. Apollo Client 4.0 works with both Expo and bare React Native projects.

bash
# Installation
npm install @apollo/client graphql

The client initialization requires an HttpLink for network requests and an InMemoryCache for local state management.

src/apollo/client.tstypescript
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';

const httpLink = new HttpLink({
  uri: 'https://api.example.com/graphql',
  // Enable text streaming for @defer and subscriptions
  fetchOptions: {
    reactNative: { textStreaming: true },
  },
});

export const apolloClient = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache({
    typePolicies: {
      Query: {
        fields: {
          // Merge paginated results
          products: {
            keyArgs: ['category'],
            merge(existing = [], incoming) {
              return [...existing, ...incoming];
            },
          },
        },
      },
    },
  }),
});

The typePolicies configuration handles cache normalization for paginated data, a common requirement in mobile apps with infinite scroll.

Wrapping the App with ApolloProvider

The provider pattern connects Apollo Client to the React component tree. Place it at the root level, typically in App.tsx or the Expo Router layout.

App.tsxtsx
import { ApolloProvider } from '@apollo/client/react';
import { apolloClient } from './src/apollo/client';
import { RootNavigator } from './src/navigation';

export default function App() {
  return (
    <ApolloProvider client={apolloClient}>
      <RootNavigator />
    </ApolloProvider>
  );
}

Apollo Client 4.0 imports React hooks from @apollo/client/react rather than the main entry point. This change reduces bundle size when the core client runs in non-React contexts.

Writing Type-Safe GraphQL Queries

GraphQL Code Generator produces TypeScript types from the schema, eliminating runtime type mismatches. Install it alongside the client plugin.

bash
# Install codegen tools
npm install -D @graphql-codegen/cli @graphql-codegen/client-preset

The codegen configuration points to the schema and specifies output locations.

codegen.tstypescript
import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  schema: 'https://api.example.com/graphql',
  documents: ['src/**/*.tsx', 'src/**/*.ts'],
  generates: {
    './src/gql/': {
      preset: 'client',
      config: {
        documentMode: 'string',
      },
    },
  },
};

export default config;

Running npx graphql-codegen generates typed document nodes that the useQuery hook consumes.

Fetching Data with useQuery

The useQuery hook manages loading states, errors, and cache updates. Apollo Client 4.0 introduces the dataState API for more predictable state transitions.

src/screens/ProductListScreen.tsxtsx
import { useQuery } from '@apollo/client/react';
import { graphql } from '../gql';
import { ActivityIndicator, FlatList, Text, View } from 'react-native';

const GET_PRODUCTS = graphql(`
  query GetProducts($category: String!, $limit: Int, $offset: Int) {
    products(category: $category, limit: $limit, offset: $offset) {
      id
      name
      price
      imageUrl
    }
  }
`);

export function ProductListScreen({ category }: { category: string }) {
  const { data, loading, error, fetchMore } = useQuery(GET_PRODUCTS, {
    variables: { category, limit: 20, offset: 0 },
    // Fetch from cache first, then network
    fetchPolicy: 'cache-and-network',
    // Keep previous data while fetching new
    notifyOnNetworkStatusChange: true,
  });

  if (error) {
    return <Text>Error: {error.message}</Text>;
  }

  const loadMore = () => {
    fetchMore({
      variables: { offset: data?.products.length ?? 0 },
    });
  };

  return (
    <FlatList
      data={data?.products ?? []}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => <ProductCard product={item} />}
      onEndReached={loadMore}
      onEndReachedThreshold={0.5}
      ListFooterComponent={loading ? <ActivityIndicator /> : null}
    />
  );
}

The cache-and-network fetch policy displays cached data immediately while refreshing from the server. This pattern reduces perceived latency on mobile networks.

Ready to ace your React Native interviews?

Practice with our interactive simulators, flashcards, and technical tests.

Mutations and Optimistic Updates

Mutations modify server data and update the local cache. Optimistic responses provide instant feedback before the server confirms the change.

src/hooks/useAddToCart.tstsx
import { useMutation } from '@apollo/client/react';
import { graphql } from '../gql';

const ADD_TO_CART = graphql(`
  mutation AddToCart($productId: ID!, $quantity: Int!) {
    addToCart(productId: $productId, quantity: $quantity) {
      id
      items {
        id
        product {
          id
          name
        }
        quantity
      }
      totalPrice
    }
  }
`);

export function useAddToCart() {
  const [addToCart, { loading }] = useMutation(ADD_TO_CART, {
    // Optimistic response for instant UI feedback
    optimisticResponse: ({ productId, quantity }) => ({
      addToCart: {
        __typename: 'Cart',
        id: 'current-cart',
        items: [],
        totalPrice: 0,
      },
    }),
    // Update cache after mutation
    update(cache, { data }) {
      if (!data?.addToCart) return;
      cache.modify({
        id: cache.identify({ __typename: 'Cart', id: 'current-cart' }),
        fields: {
          items: () => data.addToCart.items,
          totalPrice: () => data.addToCart.totalPrice,
        },
      });
    },
  });

  return { addToCart, loading };
}

The cache.modify method directly updates cached fields without refetching the entire query. This approach minimizes network requests on cellular connections.

Handling Network Errors in Mobile Apps

Apollo Client 4.0 replaces the generic ApolloError with specific error classes. The static .is() methods enable type-safe error handling.

src/components/ErrorBoundary.tsxtsx
import { NetworkError, GraphQLErrors } from '@apollo/client/errors';
import { Text, Button, View } from 'react-native';

interface Props {
  error: Error;
  onRetry: () => void;
}

export function QueryErrorHandler({ error, onRetry }: Props) {
  // Network failure (offline, timeout, DNS)
  if (NetworkError.is(error)) {
    return (
      <View>
        <Text>Connection failed. Check your internet.</Text>
        <Button title="Retry" onPress={onRetry} />
      </View>
    );
  }

  // GraphQL validation or resolver errors
  if (GraphQLErrors.is(error)) {
    const firstError = error.graphQLErrors[0];
    if (firstError?.extensions?.code === 'UNAUTHENTICATED') {
      return <Text>Session expired. Please log in again.</Text>;
    }
    return <Text>Request failed: {firstError?.message}</Text>;
  }

  return <Text>Something went wrong</Text>;
}

Distinguishing network errors from GraphQL errors allows different recovery strategies: retry for transient failures, redirect for authentication issues.

Offline Support with Cache Persistence

Mobile apps need offline access to previously fetched data. The apollo3-cache-persist library serializes the cache to AsyncStorage.

src/apollo/persistence.tstypescript
import AsyncStorage from '@react-native-async-storage/async-storage';
import { persistCache } from 'apollo3-cache-persist';
import { apolloClient } from './client';

export async function initializeCache() {
  await persistCache({
    cache: apolloClient.cache,
    storage: AsyncStorage,
    maxSize: 1024 * 1024 * 5, // 5 MB limit
    debounce: 1000, // Write at most once per second
  });
}

Call initializeCache before rendering the app to restore cached data from disk. Users see their last-fetched content immediately, even without network access.

Real-Time Updates with GraphQL Subscriptions

Subscriptions push server events to the client over WebSocket. Apollo Client 4.0 supports the newer graphql-ws protocol.

src/apollo/client.tstypescript
import { split, HttpLink, ApolloClient, InMemoryCache } from '@apollo/client';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
import { getMainDefinition } from '@apollo/client/utilities';

const httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });

const wsLink = new GraphQLWsLink(
  createClient({
    url: 'wss://api.example.com/graphql',
    connectionParams: async () => ({
      authToken: await getStoredToken(),
    }),
  })
);

// Route subscriptions to WebSocket, others to HTTP
const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  httpLink
);

export const apolloClient = new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache(),
});

The split function directs operations to the appropriate transport based on the operation type.

Common Interview Questions: React Native GraphQL

Technical interviews often probe understanding of GraphQL's mobile-specific tradeoffs. The following questions appear frequently in React Native positions.

Why choose GraphQL over REST for mobile apps?

GraphQL reduces over-fetching by returning only requested fields. A single query replaces multiple REST endpoints, reducing round trips over high-latency cellular connections. The typed schema enables compile-time validation, catching API mismatches before runtime.

How does Apollo Client cache data?

Apollo normalizes responses by __typename and id, storing each entity once. Queries reference normalized objects by cache key. This deduplication saves memory and ensures consistency: updating a product in one list updates it everywhere.

What fetch policies does Apollo Client support?

PolicyBehaviorUse Case
cache-firstReturn cache if available, else fetchDefault, minimizes requests
cache-and-networkReturn cache immediately, then update from networkFresh data with instant display
network-onlyAlways fetch, update cacheWhen stale data is unacceptable
cache-onlyNever fetch, fail if not cachedOffline mode
no-cacheFetch without cachingSensitive data

How do optimistic updates work?

The client predicts the mutation result and updates the cache before the server responds. If the server returns a different result, Apollo replaces the optimistic data with the actual response. If the mutation fails, Apollo rolls back to the previous state.

What causes an N+1 problem in GraphQL?

Nested resolvers that fetch related data individually create N+1 queries. Requesting a list of 50 products with their categories triggers 1 product query plus 50 category queries. Server-side DataLoader batches these into a single query.

How do you handle authentication tokens with Apollo Client?

The recommended approach uses an Apollo Link to attach tokens to every request.

src/apollo/authLink.tstypescript
import { setContext } from '@apollo/client/link/context';
import { getStoredToken } from '../auth/storage';

export const authLink = setContext(async (_, { headers }) => {
  const token = await getStoredToken();
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : '',
    },
  };
});

Chain this link before the HttpLink in the client configuration.

Debugging GraphQL in React Native

Apollo provides multiple debugging options for React Native development. The VS Code Apollo extension includes built-in Client DevTools that display cache contents and query history.

For standalone debugging, Flipper integrates with the community plugin react-native-apollo-devtools. This plugin inspects cache state, watches queries in flight, and replays mutations.

Network inspection through React Native Debugger requires enabling "Debug JS Remotely". The Network tab then shows GraphQL payloads with request and response bodies.

Key Takeaways for Mobile GraphQL Development

  • Apollo Client 4.0 reduces bundle size by 20-30% through ESM-first packaging and decoupled React exports.
  • The cache-and-network fetch policy balances instant display with data freshness on slow connections.
  • Optimistic updates provide immediate feedback; cache.modify avoids unnecessary refetches.
  • Typed error classes in Apollo Client 4.0 enable specific handling for network failures versus GraphQL errors.
  • Cache persistence with apollo3-cache-persist enables offline access to previously fetched data.
  • GraphQL subscriptions require the graphql-ws protocol and a split link configuration.
  • Interview questions focus on caching strategies, fetch policies, and mobile-specific tradeoffs like N+1 prevention.

Start practicing!

Test your knowledge with our interview simulators and technical tests.

Daily challenge

Can you spot the bug in React Native?

One real snippet, one hidden bug, one attempt a day. No account needed to try.

Anthony Fillion-Maillet

Written by

Anthony Fillion-Maillet

Founder of SharpSkill

Full-stack developer for over 10 years. Runs SharpSkill and answers for everything published here.

Updated on August 20, 2026

Tags

#react-native
#graphql
#apollo-client
#mobile
#api
#interview

Share

Related articles