civil-and-structural-engineering
How to Use React Native with Graphql for Efficient Data Fetching
Table of Contents
Understanding React Native and GraphQL
React Native has become a cornerstone of modern mobile development, enabling teams to ship high‑quality iOS and Android apps from a single JavaScript/TypeScript codebase. When you pair React Native with GraphQL, you gain a data‑fetching layer that is both declarative and efficient. Unlike REST, where endpoints often return fixed data structures, GraphQL lets the client specify exactly which fields it needs. This eliminates over‑fetching and under‑fetching, reduces payload sizes, and makes your mobile app faster and more data‑aware.
The synergy between React Native and a GraphQL client like Apollo Client is natural. React Native’s component‑based architecture maps beautifully to GraphQL’s query‑driven model, allowing you to colocate data requirements with UI components. The result: a mobile app that is both responsive and maintainable, even as your data model evolves.
Setting Up Your Environment
Before diving into queries and mutations, you need a working React Native project. You have two main choices:
- React Native CLI – gives you full control over native modules and is ideal for production apps that require native integrations.
- Expo – simplifies setup, provides a managed workflow, and is perfect for rapid prototyping or apps that don’t require custom native code.
For this guide, we’ll use the React Native CLI. Initialize a new project:
npx react-native init GraphQLApp
cd GraphQLApp
Now install the core Apollo Client package and GraphQL parser:
npm install @apollo/client graphql
If you plan to manage local state alongside remote GraphQL data, also consider installing @apollo/client’s reactive variables. For production apps, you may want @apollo/experimental-nextjs-app-support if you later extend to Next.js. But for a pure React Native app, the packages above are all you need.
Configuring Apollo Client
Create a dedicated file, apolloClient.js, in your project’s src folder:
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
const httpLink = createHttpLink({
uri: 'https://your-graphql-endpoint.com/graphql',
});
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
});
export default client;
Key configuration points:
- uri – your GraphQL server endpoint. For development, you can use a public API like AniList GraphQL or the SpaceX API.
- InMemoryCache – Apollo’s normalized cache stores query results and updates them incrementally, reducing network requests.
- link – allows you to chain middleware (e.g., for authentication headers, retries, or logging). If your API requires an auth token, add an
authLinkusingsetContext.
Adding Authentication Headers (Optional)
Many GraphQL APIs require a token. Use Apollo’s setContext to inject headers:
import { setContext } from '@apollo/client/link/context';
const authLink = setContext((_, { headers }) => {
const token = getToken(); // from AsyncStorage or SecureStore
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
},
};
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
This pattern is essential for apps that need to access protected resources, such as user profiles or private feeds.
Using GraphQL Queries in React Native
Wrap your root component with the ApolloProvider to make the client available throughout the component tree:
import React from 'react';
import { ApolloProvider } from '@apollo/client';
import client from './src/apolloClient';
import MainScreen from './src/screens/MainScreen';
export default function App() {
return (
<ApolloProvider client={client}>
<MainScreen />
</ApolloProvider>
);
}
Now every component inside ApolloProvider can use hooks like useQuery and useMutation.
Fetching Data with useQuery Hook
The useQuery hook is the primary way to fetch data in function components. Define your GraphQL query using the gql template literal tag:
import { useQuery, gql } from '@apollo/client';
const GET_USERS = gql`
query GetUsers {
users {
id
name
email
posts {
title
}
}
}
`;
function MainScreen() {
const { loading, error, data } = useQuery(GET_USERS);
if (loading) return <Text>Loading...</Text>;
if (error) return <Text>Error: {error.message}</Text>;
return (
<FlatList
data={data.users}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View>
<Text>{item.name} - {item.email}</Text>
</View>
)}
/>
);
}
Handling Loading & Error States Gracefully
Mobile users expect immediate feedback. Use React Native’s ActivityIndicator for loading, and consider showing a retry button on error. You can also leverage Apollo’s NetworkStatus for more granular control (e.g., loading vs refetching).
Working with GraphQL Variables
Hard‑coded queries are rarely useful. Apollo supports variable interpolation to make queries dynamic:
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
name
avatar
}
}
`;
function UserProfile({ userId }) {
const { loading, data } = useQuery(GET_USER, {
variables: { id: userId },
});
// ...
}
Variables are also essential for pagination. For example, to fetch a page of results with a cursor:
const GET_POSTS = gql`
query GetPosts($first: Int, $after: String) {
posts(first: $first, after: $after) {
edges {
node {
id
title
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}
`;
Mutations: Creating and Updating Data
GraphQL mutations allow you to modify server data. A typical mutation for creating a new user:
const ADD_USER = gql`
mutation AddUser($name: String!, $email: String!) {
addUser(name: $name, email: $email) {
id
name
}
}
`;
function AddUserForm() {
const [addUser, { loading, error }] = useMutation(ADD_USER);
const handleSubmit = async () => {
try {
await addUser({ variables: { name, email } });
// Optionally refetch queries or update cache
} catch (e) {
console.error(e);
}
};
// ...
}
After a mutation, you typically want the UI to reflect the new data. Apollo provides several strategies:
- Refetching related queries – call
refetch()on the query you just mutated. - Cache update via
updatefunction – modify the local cache manually so the app stays responsive. - Using
refetchQueries– specify an array of query names to refetch after the mutation.
Optimistic UI Updates
For a snappy user experience, you can simulate the mutation result immediately. Apollo will roll back if the server returns an error:
addUser({
variables: { name, email },
optimisticResponse: {
addUser: {
id: 'temp-id',
name,
email,
__typename: 'User',
},
},
});
Optimistic updates are especially effective in social apps or any context where instant feedback is critical.
Caching Strategies with InMemoryCache
Apollo’s cache is not just a simple store – it can be configured to match your data’s identity structure. For example, if your GraphQL API uses custom type names, you need to provide a typePolicies:
const cache = new InMemoryCache({
typePolicies: {
User: {
keyFields: ['userId'], // if the ID field is not 'id'
},
Query: {
fields: {
users: {
merge(existing, incoming) {
// Custom pagination merge logic
return incoming;
},
},
},
},
},
});
For pagination, the default InMemoryCache replaces arrays. To append new pages, use the merge function:
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
posts: {
keyArgs: false,
merge(existing = [], incoming) {
return [...existing, ...incoming];
},
},
},
},
},
});
This ensures that when you fetch the next page, the new items are appended without losing previous data.
Error Handling Best Practices
In production, network failures and server errors are inevitable. Implement robust error handling:
- Network errors – use Apollo Link’s
onErrorto log or show a toast. - GraphQL errors – check
error.graphQLErrorsfor field‑specific issues. - Retry logic – use
@apollo/client/link/retryto automatically retry failed requests.
import { onError } from '@apollo/client/link/error';
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
console.log(`[GraphQL error]: ${message}, Location: ${locations}, Path: ${path}`)
);
if (networkError) console.log(`[Network error]: ${networkError}`);
});
In React Native, consider integrating a crash reporting tool (like Sentry) to capture GraphQL errors in production.
Real‑Time Updates with Subscriptions
GraphQL subscriptions enable live updates via WebSockets. To use them in React Native, you need to install additional packages:
npm install graphql-ws @apollo/client
Configure a split link that uses HTTP for queries/mutations and WebSockets for subscriptions:
import { split, HttpLink } 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' }));
const link = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink,
);
Now components can use the useSubscription hook:
import { useSubscription, gql } from '@apollo/client';
const POST_ADDED = gql`
subscription OnPostAdded {
postAdded {
id
title
}
}
`;
function LiveFeed() {
const { data, loading } = useSubscription(POST_ADDED);
if (loading) return <Text>Waiting for updates...</Text>;
return <Text>New post: {data?.postAdded?.title}</Text>;
}
Subscriptions are perfect for chat apps, live notifications, or any feature where the data changes frequently.
Performance Optimizations
Mobile devices have limited resources. Optimize your GraphQL integration:
- Batch queries – use
@apollo/client/link/batch-httpto combine multiple requests into one HTTP call. - Persist queries – use automatic persisted queries (APQ) to reduce payload size.
- Cache‑first fetch policy – set
fetchPolicy: 'cache-first'on queries that don’t need fresh data. - Debounce search queries – avoid sending a request on every keystroke.
Example of a debounced search with Apollo:
import { useLazyQuery } from '@apollo/client';
const SEARCH_USERS = gql`
query SearchUsers($query: String!) {
searchUsers(query: $query) {
id
name
}
}
`;
function SearchBar() {
const [search, { loading, data }] = useLazyQuery(SEARCH_USERS, {
fetchPolicy: 'network-only',
});
const debouncedSearch = useRef(
debounce((value) => {
search({ variables: { query: value } });
}, 300)
).current;
return <TextInput onChangeText={debouncedSearch} />;
}
Testing GraphQL Components in React Native
Testing is vital for confidence. Use Apollo’s MockedProvider to mock queries and mutations:
import { MockedProvider } from '@apollo/client/testing';
import { render } from '@testing-library/react-native';
const mocks = [
{
request: { query: GET_USERS },
result: { data: { users: [{ id: '1', name: 'Alice' }] } },
},
];
it('renders user list', async () => {
const { findByText } = render(
<MockedProvider mocks={mocks}>
<MainScreen />
</MockedProvider>
);
const user = await findByText('Alice');
expect(user).toBeTruthy();
});
Mocking ensures your UI handles all loading, error, and data states correctly without hitting a real server.
Common Pitfalls and How to Avoid Them
- Not handling loading states in FlatList – always show an indicator when
loadingis true, especially during pagination. - Over‑fetching via nested queries – be mindful of deeply nested fields; they can bloat the response and slow down rendering.
- Ignoring cache normalization – if your GraphQL API uses a non‑standard ID field, configure
keyFieldsor you’ll get duplicate entries. - Forgetting to clean up subscriptions – subscriptions should be unsubscribed (Apollo handles this automatically when the component unmounts, but be careful with manual subscriptions).
Building a Real‑World Example
Let’s tie everything together with a simple social feed app. The app displays a list of posts, allows adding a new post with a mutation, and subscribes to new posts in real time. The full code is available on GitHub.
Highlights:
App.jswraps the app withApolloProviderconfigured with HTTP and WebSocket links.FeedScreenusesuseQuerywith pagination and auseSubscriptionto prepend new posts.AddPostFormusesuseMutationwith an optimistic update and cache write to reflect the post instantly.
Conclusion
Integrating React Native with GraphQL using Apollo Client provides a modern, efficient way to manage data in mobile apps. You’ve learned how to set up Apollo, fetch data with useQuery, handle mutations, optimize caching, and even add real‑time features with subscriptions. By following the patterns described here, you can build scalable, maintainable mobile applications that delight users with fast load times and seamless interactions. Start experimenting today – perhaps by converting an existing REST‑driven React Native app to GraphQL – and experience the difference firsthand.