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.

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 uses wsLink; otherwise falls back to httpLink.

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: loading is true while 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 onError in the WebSocketLink options.
  • 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 WebSocketLink and 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.memo on 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

MethodLatencyServer LoadBattery/Data UsageImplementation Complexity
Polling (HTTP every N seconds)High (delay = interval)High (many requests)High (frequent wake-ups)Low
Server-Sent Events (SSE)LowLower than pollingModerateMedium (not natively supported in React Native without polyfill)
WebSocket + GraphQL SubscriptionsVery LowLow (only pushes changes)Low (persistent connection with minimal overhead)Medium-High (setup, auth)
WebSocket custom protocolVery LowLowLowHigh (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 ErrorBoundary to 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: replace subscriptions-transport-ws with graphql-ws and use GraphQLWsLink from @apollo/client/link/subscriptions.
  • Cache subscription data – update local state or Apollo cache carefully to avoid stale data. The update callback 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