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.
Prerequisites
- 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
andhistory
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
CopyCopied!
- 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
CopyCopied!
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
CopyCopied!
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
CopyCopied!
Replace the contents of App.tsx
with the following:
// 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>
);
}
CopyCopied!
Install the Ably Pub/Sub JavaScript SDK in your React Native project:
npm install ably
CopyCopied!
Set up AblyProvider
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
:
// 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 OnlyCopyCopied!
Step 1: Connect to Ably
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:
const realtimeClient = new Ably.Realtime({
key: '<loading API key, please wait>',
clientId: 'my-first-client',
});
Demo OnlyCopyCopied!
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:
// 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>;
}
CopyCopied!
Update your App
component in the App.tsx
file to include the ConnectionState
component:
// 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 OnlyCopyCopied!
Now start your application by running:
npm run start
CopyCopied!
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.
ChannelProvider
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
:
// 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 OnlyCopyCopied!
Subscribe to a channel
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
:
// 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>
);
}
CopyCopied!
Next, update your main App
component in the App.tsx
file to include the Messages
component within the ChannelProvider
:
// 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 OnlyCopyCopied!
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!'
CopyCopied!
Publish a message
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:
// 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>
);
}
CopyCopied!
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!'
CopyCopied!
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:
// 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>
);
}
CopyCopied!
Add the PresenceStatus
component to your main App
component in App.tsx
as follows:
// 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>
);
}
CopyCopied!
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"}'
CopyCopied!
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:
// 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 ();
}
CopyCopied!
Test this feature with the following steps:
- 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}}"
CopyCopied!
- Refresh the app. This will cause the
Messages
component to mount again and call thechannel.history()
method. - 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
CopyCopied!
Step 5: Close the connection
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.
const realtimeClient = useAbly();
const handleDisconnect = () => {
realtimeClient.connection.close();
};
// Call handleDisconnect when needed
CopyCopied!
This ensures the connection is closed when your component unmounts, freeing resources and removing listeners.
Next steps
Continue to explore the Ably Pub/Sub documentation with React as the selected language:
Read more about the concepts covered in this guide:
- Revisit the basics of Pub/Sub
- Explore more advanced Pub/Sub concepts
- Understand realtime connections to Ably
- Read more about how to use presence in your apps
- Fetch message history in your apps
You can also explore the Ably CLI further, or visit the Pub/Sub API references.