civil-and-structural-engineering
Using React Native with Graphql Subscriptions for Real-time Data Updates
Table of Contents
Introduction to Real-time Data in React Native Mobile Apps
Building mobile applications that respond instantly to data changes is no longer a luxury—it’s an expectation. Users want to see new chat messages appear without refreshing, live scores update on the fly, and stock prices stream in without delay. React Native, paired with GraphQL subscriptions, delivers exactly that kind of interactive experience. While GraphQL queries and mutations handle request-response cycles, subscriptions open a persistent, bidirectional channel—usually over WebSocket—allowing the server to push data the moment it becomes available. This article provides a deep, production-ready guide to implementing GraphQL subscriptions in React Native, covering setup, authentication, error handling, and best practices.
Understanding GraphQL Subscriptions
GraphQL subscriptions are a core part of the specification, designed for event-driven data flows. Unlike queries that pull data once, subscriptions maintain an active connection. When a relevant event occurs on the server (e.g., a new message, a database update), the server sends the new data to all subscribed clients. This mechanism relies on WebSocket as the transport layer, though other transports like Server-Sent Events (SSE) can be used with additional tooling.
How Subscriptions Differ from Queries and Mutations
- Queries: HTTP POST/GET, request-response, no persistent connection. Best for initial page load or on-demand data fetching.
- Mutations: Also HTTP-based, designed to modify data and return the updated result.
- Subscriptions: Persistent connection (WebSocket), server pushes data to client when events occur. Ideal for live feeds, notifications, collaborative editing, and real-time dashboards.
Use Cases for Subscriptions in Mobile Apps
- Chat applications – users receive messages instantly.
- Live event updates – sports scores, auction bids.
- Real-time collaboration – shared document editing, task board changes.
- Notifications – push alerts based on server-side triggers.
Setting Up the React Native Environment
Start by initializing a React Native project (Expo or bare workflow). Then install the required dependencies:
yarn add @apollo/client graphql
yarn add subscriptions-transport-ws
For TypeScript support, add the type definitions:
yarn add -D @types/subscriptions-transport-ws
@apollo/client provides the core functionality, cache management, and hooks (including useSubscription). subscriptions-transport-ws is a WebSocket client for GraphQL subscriptions. Alternatively, you can use graphql-ws (a newer WebSocket implementation) by swapping the link package.
Configuring Apollo Client with WebSocket and HTTP Links
Apollo Client needs to distinguish between subscriptions (WebSocket) and queries/mutations (HTTP). The split function routes operations based on their definition type. Here’s the production-grade setup:
import { ApolloClient, InMemoryCache, split } from '@apollo/client';
import { WebSocketLink } from '@apollo/client/link/ws';
import { HttpLink } from '@apollo/client/link/http';
import { getMainDefinition } from '@apollo/client/utilities';
const httpLink = new HttpLink({
uri: 'https://your-graphql-server.com/graphql',
headers: {
// include auth token if needed
authorization: `Bearer ${userToken}`,
},
});
const wsLink = new WebSocketLink({
uri: 'wss://your-graphql-server.com/graphql',
options: {
reconnect: true,
connectionParams: {
// authentication token for subscriptions
authToken: userToken,
},
},
});
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
);
const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});
Key Configuration Details
- WebSocket URL: Use
wss://for secure connections;ws://for local development. reconnect: true: Automatically reconnects if the WebSocket connection drops. Exponential backoff is built-in.connectionParams: Pass authentication tokens, user IDs, or other handshake data required by your server.split: Inspects each operation. If it’s a subscription, it useswsLink; otherwise falls back tohttpLink.
For apps that require persistent authentication, update connectionParams dynamically by creating a function:
connectionParams: () => ({
authToken: AsyncStorage.getItem('authToken'),
})
Using the useSubscription Hook in Components
Once the client is configured, wrap your React Native root component with ApolloProvider:
import { ApolloProvider } from '@apollo/client';
const App = () => (
<ApolloProvider client={client}>
<MainScreen />
</ApolloProvider>
);
Now define your subscription and use the hook:
import { gql, useSubscription } from '@apollo/client';
const ON_NEW_MESSAGE = gql`
subscription OnNewMessage($channelId: ID!) {
newMessage(channelId: $channelId) {
id
content
sender {
username
}
createdAt
}
}
`;
function MessageFeed({ channelId }) {
const { data, loading, error } = useSubscription(ON_NEW_MESSAGE, {
variables: { channelId },
});
if (loading) {
return <ActivityIndicator size="large" />;
}
if (error) {
return <Text>Subscription error: {error.message}</Text>;
}
return (
<View>
<Text>New message from {data.newMessage.sender.username}: {data.newMessage.content}</Text>
</View>
);
}
State Management and Side Effects
- Loading:
loadingistruewhile the subscription is not yet established (initial handshake). - Error: Fires if the server rejects the subscription or a network error occurs. You can catch errors globally using
onErrorin theWebSocketLinkoptions. - Data: Updates every time the subscription receives a new event. The component re-renders automatically.
If you need to update an existing list (e.g., append new message to a FlatList), use the update option in the useSubscription hook or combine with a local state reducer.
Advanced Patterns for Real-world Applications
Updating Local State with Subscription Data
Often you’ll want to merge subscription data into a cached list. Use the update callback:
const [messages, setMessages] = useState([]);
useSubscription(ON_NEW_MESSAGE, {
variables: { channelId },
onSubscriptionData: ({ subscriptionData }) => {
const newMessage = subscriptionData.data.newMessage;
setMessages((prev) => [...prev, newMessage]);
},
});
Alternatively, update the Apollo cache directly using writeFragment or modify inside the update callback – this keeps the cache consistent across all components.
Multiple Subscriptions in One Component
You can call useSubscription multiple times or define a single subscription with multiple fields. For clarity, keep separate subscriptions if the events are logically distinct:
const { data: onlineData } = useSubscription(ON_USER_ONLINE);
const { data: messageData } = useSubscription(ON_NEW_MESSAGE, { variables });
Dynamic Subscription Parameters
When the user switches channels, you need to unsubscribe from the old subscription and subscribe to a new one. Apollo handles this automatically when the variables change – it tears down the old WebSocket channel and establishes a new one.
Authentication and Authorization in Subscriptions
Securing subscriptions follows the same principles as queries, but requires extra attention because the connection is long-lived.
Sending JWT Tokens in the Connection Handshake
In the WebSocketLink configuration, provide connectionParams with the token. Your GraphQL server should validate the token during the connection_init event and reject invalid tokens.
Reauthentication on Token Expiry
If tokens expire, the subscription will be terminated. Implement a reconnection strategy:
- Periodically refresh the token (use a refresh token pattern).
- When the token changes, recreate the
WebSocketLinkand consequently the Apollo Client. - Notify the user if the session expires and force re-login.
Some libraries like graphql-ws allow setting a lazyCloseTimeout to automatically reconnect after token refresh.
Error Handling and Reconnection Best Practices
Global Error Listener
Add an onError callback to the WebSocketLink:
const wsLink = new WebSocketLink({
uri: 'wss://...',
options: {
reconnect: true,
onError: (error) => {
console.error('WebSocket error', error);
// Optionally show a toast or snackbar to the user
},
onReconnected: () => {
console.log('WebSocket reconnected');
// Dismiss any error indicator
},
},
});
Graceful Degradation
If the WebSocket connection fails entirely, fall back to polling. However, for most real-time features, a persistent connection is expected. In scenarios like poor network, consider showing a “Reconnecting…” indicator.
Subscription Lifecycle
Apollo Client automatically unsubscribes when the component unmounts or variables change. You can manually start/stop a subscription by using subscribeToMore on a query result, but useSubscription is simpler for standalone subscriptions.
Performance Considerations
Minimizing Re-renders
- Only subscribe to the specific fields you need – avoid fetching large objects that will cause the component to re-render unnecessarily.
- Use
React.memoon components that receive subscription data. - Throttle or debounce the subscription if the server emits events very frequently (e.g., mouse movements).
Batching and Deduplication
If multiple components subscribe to the same subscription, Apollo Client deduplicates the actual WebSocket messages – only one connection is made per unique subscription definition and variables. This is handled automatically by the cache and the split link.
Memory Management
Each subscription keeps a WebSocket connection open. If your app has many screens with active subscriptions (e.g., a dashboard with multiple live panels), ensure that you unsubscribe from inactive ones. Apollo does this when the component unmounts, but if you store subscriptions outside of React, manage them explicitly.
Comparison with Polling and Other Real-time Approaches
| Method | Latency | Server Load | Battery/Data Usage | Implementation Complexity |
|---|---|---|---|---|
| Polling (HTTP every N seconds) | High (delay = interval) | High (many requests) | High (frequent wake-ups) | Low |
| Server-Sent Events (SSE) | Low | Lower than polling | Moderate | Medium (not natively supported in React Native without polyfill) |
| WebSocket + GraphQL Subscriptions | Very Low | Low (only pushes changes) | Low (persistent connection with minimal overhead) | Medium-High (setup, auth) |
| WebSocket custom protocol | Very Low | Low | Low | High (build your own message schema) |
For most real-time features in a React Native app, GraphQL subscriptions offer the best balance of latency, efficiency, and developer experience, especially if you already use GraphQL for queries and mutations.
Best Practices for Production Apps
- Always provide error boundaries – wrap subscription components in an
ErrorBoundaryto prevent crashes from unexpected subscription errors. - Test across networks – simulate offline, slow 3G, and authentication expiry scenarios.
- Use environment variables – keep WebSocket URLs and connection parameters configurable per environment.
- Monitor WebSocket connections – use your APM (Application Performance Monitoring) tool to watch for frequent disconnections.
- Consider
graphql-ws– it’s the newer, more WebSocket-standard-compliant library. Migration is straightforward: replacesubscriptions-transport-wswithgraphql-wsand useGraphQLWsLinkfrom@apollo/client/link/subscriptions. - Cache subscription data – update local state or Apollo cache carefully to avoid stale data. The
updatecallback is your friend.
Conclusion
GraphQL subscriptions turn a static mobile app into a live, responsive experience. When integrated with React Native and Apollo Client, the process is streamlined: set up a WebSocket link, split the routing from HTTP, and use the useSubscription hook to react to real-time events. Beyond the fundamentals, this guide covered advanced patterns like authentication, error recovery, cache updates, and performance tuning – all essential for production-level applications. By adopting subscriptions, you reduce unnecessary network traffic, keep your users engaged, and write cleaner code than custom-push solutions. Start by adding a simple subscription to your next React Native project; the improvement in user experience will speak for itself.
Further reading: Apollo Client Subscriptions Documentation | GraphQL Subscriptions Specification | React Native Networking