Getting started: Pub/Sub with React Native

This guide will get you started with Ably Pub/Sub in a new React Native application built with Expo.

It will take you through the following steps:

  • Create a client and establish a realtime connection to Ably.
  • Attach to a channel and subscribe to its messages.
  • Publish a message to the channel for your client to receive.
  • Join and subscribe to the presence set of the channel.
  • Retrieve the messages you sent in the guide from history.
  • Close a connection to Ably when it is no longer needed.

  • Sign up for a free Ably account.
    • Create a new app and get your API key from the dashboard.
    • Your API key will need the publish, subscribe, presence and history capabilities.

This step is optional. If you prefer not to use the Ably CLI to interact with your Pub/Sub React Native application, you can still follow along by using multiple browser windows, mobile devices or emulators.

npm install -g @ably/cli
Copied!
  • Run the following to log in to your Ably account and set the default app and API key:
ably login ably apps switch ably auth keys switch
Copied!

Create a new React Native project using Expo using Nativewind for styling and then navigate to the project folder:

npx rn-new@latest ably-pubsub-react-native --nativewind cd ably-pubsub-react-native
Copied!

To open your project on the web, you also need to install the following required dependencies:

npx expo install react-dom react-native-web @expo/metro-runtime
Copied!

Replace the contents of App.tsx with the following:

React v2.9
// App.tsx import { StatusBar } from 'expo-status-bar'; import { Text, View } from 'react-native'; import './global.css'; export default function App() { return ( <View className="mt-8 w-full flex-1 items-center"> <View className="w-[90%] max-w-[900px] rounded-lg border border-blue-500"> <View className="w-full rounded-lg bg-gray-100 p-4"> <Text className="text-center text-lg font-semibold text-blue-500"> Ably Pub/Sub React Native </Text> </View> </View> <StatusBar style="auto" /> </View> ); }
Copied!

Install the Ably Pub/Sub JavaScript SDK in your React Native project:

npm install ably
Copied!

The Ably Pub/Sub SDK provides React hooks and context providers that make it easier to use Pub/Sub features in your React Native components.

The AblyProvider component should be used at the top level of your application, typically in App.tsx. It provides access to the Ably Realtime client for all child components that use Ably Pub/Sub React hooks.

Replace the contents of your App.tsx file with the following code to set up the AblyProvider:

React v2.9
// App.tsx import * as Ably from 'ably'; import { AblyProvider } from 'ably/react'; import { StatusBar } from 'expo-status-bar'; import { SafeAreaView, Text, View } from 'react-native'; import './global.css'; // Create your Ably Realtime client const realtimeClient = new Ably.Realtime({ key: '<loading API key, please wait>', clientId: 'my-first-client', }); export default function App() { return ( <AblyProvider client={realtimeClient}> <SafeAreaView className="flex-1 bg-white"> <View className="mt-8 w-full flex-1 items-center"> <View className="w-[90%] max-w-[900px] rounded-lg border border-blue-500"> <View className="w-full rounded-lg bg-gray-100 p-4"> <Text className="text-center text-lg font-semibold text-blue-500"> Ably Pub/Sub React Native </Text> </View> </View> <StatusBar style="auto" /> </View> </SafeAreaView> </AblyProvider> ); }
Demo Only
Copied!

Clients establish a connection with Ably when they instantiate an SDK instance. This enables them to send and receive messages in realtime across channels.

In the Set up AblyProvider section, you added the following code to create an Ably Realtime client:

React v2.9
const realtimeClient = new Ably.Realtime({ key: '<loading API key, please wait>', clientId: 'my-first-client', });
Demo Only
Copied!

This code creates a new Realtime client instance, establishing a connection to Ably when your application starts. At the minimum you need to provide an authentication mechanism. While using an API key is fine for the purposes of this guide, you should use token authentication in production environments. A clientId ensures the client is identified, which is required to use certain features, such as presence.

To monitor the Ably connection state within your application, create a component that uses the useConnectionStateListener() hook provided by the Ably Pub/Sub SDK. This hook must be nested inside an AblyProvider, so the component must be placed within the AblyProvider in your application.

In your project, create a new file components/ConnectionState.tsx with the following content:

React v2.9
// components/ConnectionState.tsx // React hooks are exported from the 'ably/react' path of the 'ably' package. import { useAbly, useConnectionStateListener } from 'ably/react'; import { Text } from 'react-native'; import { useState } from 'react'; export function ConnectionState() { // This component displays the current connection state // The useAbly hook returns the Ably Realtime client instance provided by the AblyProvider const ably = useAbly(); const [connectionState, setConnectionState] = useState(ably.connection.state); // useConnectionStateListener hook listens for changes in connection state useConnectionStateListener((stateChange) => { setConnectionState(stateChange.current); }); return <Text className="mt-4 text-center">Connection: {connectionState}!</Text>; }
Copied!

Update your App component in the App.tsx file to include the ConnectionState component:

React v2.9
// App.tsx import * as Ably from 'ably'; import { AblyProvider } from 'ably/react'; import { StatusBar } from 'expo-status-bar'; import { SafeAreaView, Text, View } from 'react-native'; import { ConnectionState } from './components/ConnectionState'; import './global.css'; // Create your Ably Realtime client const realtimeClient = new Ably.Realtime({ key: '<loading API key, please wait>', clientId: 'my-first-client', }); export default function App() { return ( <AblyProvider client={realtimeClient}> <SafeAreaView className="flex-1 bg-white"> <View className="mt-8 w-full flex-1 items-center"> <View className="w-[90%] max-w-[900px] rounded-lg border border-blue-500"> <View className="w-full rounded-lg bg-gray-100 p-4"> <Text className="text-center text-lg font-semibold text-blue-500"> Ably Pub/Sub React Native </Text> {/* Add ConnectionState here */} <ConnectionState /> </View> </View> <StatusBar style="auto" /> </View> </SafeAreaView> </AblyProvider> ); }
Demo Only
Copied!

Now start your application by running:

npm run start
Copied!

Open your React Native app on a physical device by scanning the QR code in your console, or run it locally using the iOS Simulator, Android Emulator, or on the web by typing i, a, or w respectively in your console.

You should see the connection state displayed in your UI (e.g., Connection: connected!). You can also inspect connection events in the dev console of your app.

Messages contain the data that a client is communicating, such as a short ‘hello’ from a colleague, or a financial update being broadcast to subscribers from a server. Ably uses channels to separate messages into different topics, so that clients only ever receive messages on the channels they are subscribed to.

Now that you’re connected to Ably, you can create and manage channels using the ChannelProvider component from the Ably Pub/Sub SDK. This component must be nested within the AblyProvider described above.

Update your main App component to include the ChannelProvider:

React v2.9
// App.tsx import * as Ably from 'ably'; import { AblyProvider, ChannelProvider } from 'ably/react'; import { StatusBar } from 'expo-status-bar'; import { SafeAreaView, Text, View } from 'react-native'; import { ConnectionState } from './components/ConnectionState'; import './global.css'; // Create your Ably Realtime client const realtimeClient = new Ably.Realtime({ key: '<loading API key, please wait>', clientId: 'my-first-client', }); export default function App() { return ( <AblyProvider client={realtimeClient}> {/* Wrap components with ChannelProvider */} <ChannelProvider channelName="my-first-channel"> ... </ChannelProvider> </AblyProvider> ); }
Demo Only
Copied!

Use the useChannel() hook within the ChannelProvider component to subscribe to incoming messages on a channel. This hook also provides access to a channel instance and a publish method for sending messages.

In your project, create a new file called components/Messages.tsx and add new components called Messages and MessageView:

React v2.9
// components/Messages.tsx import type { Message } from 'ably'; import { useChannel } from 'ably/react'; import { useState } from 'react'; import { ScrollView, Text, View } from 'react-native'; function MessageView({ message }: { message: Message }) { // Displays an individual message const isMine = message.clientId === 'my-first-client'; return ( <View className={`my-1 rounded px-2 py-1 shadow-sm ${isMine ? 'bg-green-100' : 'bg-blue-50'}`}> <Text className="text-gray-800">{message.data}</Text> </View> ); } export function Messages() { const [messages, setMessages] = useState<Message[]>([]); // The useChannel hook subscribes to messages on the channel useChannel('my-first-channel', (message) => { setMessages((prevMessages) => [...prevMessages, message]); }); return ( <View className="h-[200px] w-full rounded-lg lg:h-[400px]"> <ScrollView className="flex-1 p-4"> {messages.map((msg: Message) => ( <MessageView key={msg.id} message={msg} /> ))} </ScrollView> </View> ); }
Copied!

Next, update your main App component in the App.tsx file to include the Messages component within the ChannelProvider:

React v2.9
// App.tsx import * as Ably from 'ably'; import { AblyProvider, ChannelProvider } from 'ably/react'; import { StatusBar } from 'expo-status-bar'; import { SafeAreaView, Text, View } from 'react-native'; import { ConnectionState } from './components/ConnectionState'; import { Messages } from './components/Messages'; import './global.css'; // Create your Ably Realtime client const realtimeClient = new Ably.Realtime({ key: '<loading API key, please wait>', clientId: 'my-first-client', }); export default function App() { return ( <AblyProvider client={realtimeClient}> <ChannelProvider channelName="my-first-channel"> <SafeAreaView className="flex-1 bg-white"> <View className="mt-8 w-full flex-1 items-center"> <View className="w-[90%] max-w-[900px] rounded-lg border border-blue-500"> <View className="w-full rounded-lg bg-gray-100 p-4"> <Text className="text-center text-lg font-semibold text-blue-500"> Ably Pub/Sub React Native </Text> <ConnectionState /> </View> <View className="flex-column justify-evenly lg:flex-row"> <View className="lg:w-3/4"> {/* Your Messages component should go here */} <Messages /> </View> </View> </View> <StatusBar style="auto" /> </View> </SafeAreaView> </ChannelProvider> </AblyProvider> ); }
Demo Only
Copied!

You’ve successfully created a channel instance and set up a listener to receive messages. You can test this immediately by publishing messages using the Ably CLI:

ably channels publish my-first-channel 'Hello from CLI!'
Copied!

You can publish messages in your React Native app using the publish method provided by the useChannel() hook.

Update your components/Messages.tsx file to include message publishing:

React v2.9
// components/Messages.tsx import type { Message } from 'ably'; import { useChannel } from 'ably/react'; import { useState } from 'react'; import { ScrollView, Text, TextInput, TouchableOpacity, View } from 'react-native'; function MessageView({ message }: { message: Message }) { // Displays an individual message const isMine = message.clientId === 'my-first-client'; return ( <View className={`my-1 rounded px-2 py-1 shadow-sm ${isMine ? 'bg-green-100' : 'bg-blue-50'}`}> <Text className="text-gray-800">{message.data}</Text> </View> ); } export function Messages() { const [messages, setMessages] = useState<Message[]>([]); const [inputValue, setInputValue] = useState(''); // useChannel hook also provides a publish method const { publish } = useChannel('my-first-channel', (message) => { setMessages((prevMessages) => [...prevMessages, message]); }); // Function to handle publishing messages const handlePublish = () => { if (!inputValue.trim()) return; publish('my-first-messages', inputValue.trim()).catch((err) => console.error('Error publishing message', err) ); setInputValue(''); }; return ( <View className="h-[200px] w-full rounded-lg lg:h-[400px]"> <ScrollView className="flex-1 p-4"> {messages.map((msg: Message) => ( <MessageView key={msg.id} message={msg} /> ))} </ScrollView> <View className="mb-2 mt-auto flex-row items-center border-t border-gray-200 p-2"> <TextInput placeholder="Type your message..." className="flex-1 rounded border border-gray-400 bg-white p-2" value={inputValue} onChangeText={setInputValue} onSubmitEditing={handlePublish} /> <TouchableOpacity className="ml-2 items-center justify-center rounded bg-blue-500 px-4 py-2" onPress={handlePublish}> <Text className="text-white">Publish</Text> </TouchableOpacity> </View> </View> ); }
Copied!

Your application now supports publishing realtime messages! Type a message and press “Publish” to see it appear in your UI. Open the app on a second device or simulator to see clients interacting with each other in realtime, or publish messages using the Ably CLI:

ably channels publish my-first-channel 'Hello from CLI!'
Copied!

Messages from the CLI will appear in your UI in a different color to the ones you sent from the app.

Presence enables clients to be aware of one another if they are present on the same channel. You can then show clients who else is online, provide a custom status update for each, and notify the channel when someone goes offline.

Use the usePresence() and usePresenceListener() hooks provided by the Ably Pub/Sub SDK to interact with the presence feature in your React Native application. The usePresence() hook enables a client to join the presence set on a channel and update their presence status. The usePresenceListener() hook lets you subscribe to presence changes on a channel.

The usePresenceListener() hook also returns an object containing the presenceData array, which holds current presence data on the channel.

Create a new file called components/PresenceStatus.tsx with the following content:

React v2.9
// components/PresenceStatus.tsx // 'ably/react' exports hooks for working with presence on a channel import { usePresence, usePresenceListener } from 'ably/react'; import { Text, View } from 'react-native'; export function PresenceStatus() { // Enter the current client into the presence set with an optional status usePresence('my-first-channel', { status: "I'm here!" }); // Subscribe to presence updates on the channel const { presenceData } = usePresenceListener('my-first-channel'); return ( <View className="w-full bg-white px-4 py-2"> <Text className="mb-2 border-b border-gray-200 pb-2 text-center text-green-700"> Present: {presenceData.length} </Text> <View> {presenceData.map((member, idx) => ( <View key={idx} className="mb-2 flex-row items-center"> <View className="mr-2 h-2 w-2 rounded-full bg-green-500" /> <Text className="text-gray-800"> {member.clientId} {member.data?.status ? ` (${member.data.status})` : ''} </Text> </View> ))} </View> </View> ); }
Copied!

Add the PresenceStatus component to your main App component in App.tsx as follows:

React v2.9
// App.tsx // Add PresenceStatus import import { PresenceStatus } from './components/PresenceStatus'; // Other imports and realtimeClient initialization export default function App() { return ( <AblyProvider client={realtimeClient}> <ChannelProvider channelName="my-first-channel"> <SafeAreaView className="flex-1 bg-white"> <View className="mt-8 w-full flex-1 items-center"> <View className="w-[90%] max-w-[900px] rounded-lg border border-blue-500"> <View className="w-full rounded-lg bg-gray-100 p-4"> <Text className="text-center text-lg font-semibold text-blue-500"> Ably Pub/Sub React Native </Text> <ConnectionState /> </View> <View className="flex-column justify-evenly lg:flex-row"> <View className="border-blue-500 max-lg:border-b lg:w-1/4 lg:border-r"> {/* Your PresenceStatus component should go here */} <PresenceStatus /> </View> <View className="lg:w-3/4"> <Messages /> </View> </View> </View> <StatusBar style="auto" /> </View> </SafeAreaView> </ChannelProvider> </AblyProvider> ); }
Copied!

The application will now display a list of clients currently present on the channel. The usePresence() hook enters your client into the channel’s presence set with an optional status, while the usePresenceListener() hook subscribes to presence updates. Your current client ID should appear in the list of online users.

You can have another client join the presence set using the Ably CLI:

ably channels presence enter my-first-channel --client-id "my-cli" --profile-data '{"status":"From CLI"}'
Copied!

You can retrieve previously sent messages using the history feature. Ably stores all messages for 2 minutes by default in the event a client experiences network connectivity issues. This can be extended for longer if required.

Although the Ably Pub/Sub SDK does not provide a specific hook for retrieving message history, you can use the useChannel() hook to get a RealtimeChannel instance and then call its history() method to retrieve messages recently published to the channel.

Update your components/Messages.tsx file to include the new useEffect within your existing Messages component:

React v2.9
// components/Messages.tsx // Update your react import to have `useEffect` as well. import { useState, useEffect } from 'react'; // MessageView component remains unchanged export function Messages() { // State management remains the same // Include channel from useChannel hook const { channel, publish } = useChannel('my-first-channel', (message) => { setMessages((prevMessages) => [...prevMessages, message]); }); // handlePublish function remains unchanged useEffect(() => { async function loadHistory() { try { // Retrieve the last 5 messages from history const history = await channel.history({ limit: 5 }); // History responses are returned in reverse chronological order (newest first) // Reverse the array to show the latest messages at the bottom in the UI const messagesFromHistory = history.items.reverse(); // Update the state with retrieved messages setMessages(messagesFromHistory); } catch (error) { console.error('Error loading message history:', error); } } loadHistory(); }, [channel]); // Return remains unchanged return (); }
Copied!

Test this feature with the following steps:

  1. Publish several messages using your application UI, or send messages from another client using the Ably CLI:
ably channels publish --count 5 my-first-channel "Message number {{.Count}}"
Copied!
  1. Refresh the app. This will cause the Messages component to mount again and call the channel.history() method.
  2. You should see the last 5 messages displayed in your UI, ordered from oldest to newest at the bottom:
Message number 1 Message number 2 Message number 3 Message number 4 Message number 5
Copied!

Connections are automatically closed approximately two minutes after the last channel is detached. However, explicitly closing connections when they’re no longer needed is good practice to help save costs and clean up listeners.

React v2.9
const realtimeClient = useAbly(); const handleDisconnect = () => { realtimeClient.connection.close(); }; // Call handleDisconnect when needed
Copied!

This ensures the connection is closed when your component unmounts, freeing resources and removing listeners.

Continue to explore the Ably Pub/Sub documentation with React as the selected language:

Read more about the concepts covered in this guide:

You can also explore the Ably CLI further, or visit the Pub/Sub API references.

Select...