Getting started: Pub/Sub with React

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

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 an Ably account.
    • Create a new app, and create your first API key.
    • Your API key will need the publish, subscribe, presence and history capabilities.
  • Install the Ably CLI:
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 + TypeScript project using Vite. Then, navigate to the project folder and install the dependencies:

npm create vite@latest ably-pubsub-react -- --template react-ts │ ◇ Scaffolding project in /ably-pubsub-react... │ └ Done. Now run: cd ably-pubsub-react npm install
Copied!

You should see a directory structure similar to this:

├── index.html ├── package.json ├── public ├── src │ ├── assets │ ├── App.css │ ├── App.tsx │ ├── index.css │ ├── main.tsx │ └── vite-env.d.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts
Copied!

You will also need to setup Tailwind CSS for styling the application.

First, install the required Tailwind CSS packages:

npm install tailwindcss @tailwindcss/vite
Copied!

Next, update vite.config.ts file to include the Tailwind CSS plugin:

React v2.9
// vite.config.ts import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import tailwindcss from '@tailwindcss/vite'; // https://vite.dev/config/ export default defineConfig({ plugins: [react(), tailwindcss()], });
Copied!

Finally, import Tailwind CSS in the src/index.css file and remove all other existing CSS styles:

Select...
/* src/index.css */ @import 'tailwindcss';
Copied!

And replace the contents of src/App.tsx with the following:

React v2.9
// src/App.tsx function App() { return ( <div className='flex flex-col w-[900px] h-full border-1 border-blue-500 rounded-lg overflow-hidden mx-auto font-sans'> <div className='flex flex-row w-full rounded-lg overflow-hidden mx-auto font-sans'> <div className='flex-1 bg-gray-100 text-center p-4'> <h2 className='text-lg font-semibold text-blue-500'> Ably Pub/Sub React </h2> </div> </div> </div> ); } export default App;
Copied!

Install the Ably Pub/Sub JavaScript SDK in your React 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 components.

The AblyProvider component should be used at the top level of your application, typically in main.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 src/main.tsx file with the following code to set up the AblyProvider:

React v2.9
// src/main.tsx import * as Ably from 'ably'; import { AblyProvider } from 'ably/react'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import App from './App.tsx'; import './index.css'; // Create your Ably Realtime client const realtimeClient = new Ably.Realtime({ key: '<loading API key, please wait>', clientId: 'my-first-client', }); createRoot(document.getElementById('root')!).render( <StrictMode> <AblyProvider client={realtimeClient}> <App /> </AblyProvider> </StrictMode> );
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.

Open up the dev console of your first app before instantiating your client so that you can see what happens.

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 src/ConnectionState.tsx with the following content:

React v2.9
// src/ConnectionState.tsx // React hooks are exported from the 'ably/react' path of the 'ably' package. import { useAbly, useConnectionStateListener } from 'ably/react'; 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 ( <div className='mt-4 text-center h-full'> <p>Connection: {connectionState}!</p> </div> ); }
Copied!

Then, update your App component in the src/App.tsx file to include the ConnectionState component:

React v2.9
// src/App.tsx // Import your newly created component import { ConnectionState } from './ConnectionState'; function App() { return ( <div className='flex flex-col w-[900px] h-full border-1 border-blue-500 rounded-lg overflow-hidden mx-auto font-sans'> <div className='flex flex-row w-full rounded-lg overflow-hidden mx-auto font-sans'> <div className='flex-1 bg-gray-100 text-center p-4'> <h2 className='text-lg font-semibold text-blue-500'> Ably Pub/Sub React </h2> {/* Add ConnectionState here */} <ConnectionState /> </div> </div> </div> ); } export default App;
Copied!

Now run your application by starting the development server:

npm run dev
Copied!

Open the URL shown in the terminal (typically http://localhost:5173/).

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
// src/App.tsx import { ChannelProvider } from 'ably/react'; import { ConnectionState } from './ConnectionState'; function App() { return ( // Wrap components with ChannelProvider <ChannelProvider channelName='my-first-channel'> ... </ChannelProvider> ); } export default App;
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 src/Messages.tsx and add new components called Messages and MessageView:

React v2.9
// src/Messages.tsx import type { Message } from 'ably'; import { useChannel } from 'ably/react'; import { useState } from 'react'; function MessageView({ message }: { message: Message }) { // Displays an individual message const isMine = message.clientId === 'my-first-client'; return ( <p className={`py-1 px-2 shadow-sm ${ isMine ? 'bg-green-100 text-gray-800' : 'bg-blue-50 text-gray-800' }`} > {message.data} </p> ); } 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 ( <div className='flex flex-col w-full h-[600px] item-left rounded-lg overflow-hidden mx-auto font-sans'> <div className='flex-1 p-4 overflow-y-auto space-y-2'> {messages.map((msg: Message) => ( <MessageView key={msg.id} message={msg} /> ))} </div> </div> ); }
Copied!

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

React v2.9
// src/App.tsx import { ChannelProvider } from 'ably/react'; import { ConnectionState } from './ConnectionState'; import { Messages } from './Messages'; function App() { return ( <ChannelProvider channelName='my-first-channel'> <div className='flex flex-col w-[900px] h-full border-1 border-blue-500 rounded-lg overflow-hidden mx-auto font-sans'> ... <div className='flex flex-1 flex-row justify-evenly'> <div className='flex flex-col bg-white w-3/4 rounded-lg overflow-hidden mx-auto font-sans'> {/* Your Messages component should go here */} <Messages /> </div> </div> </div> </ChannelProvider> ); } export default App;
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 app using the publish method provided by the useChannel() hook.

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

React v2.9
// src/Messages.tsx // existing message imports and MessageView function 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 ( <div className='flex flex-col w-full h-[600px] item-left rounded-lg overflow-hidden mx-auto font-sans'> <div className='flex-1 p-4 overflow-y-auto space-y-2'> {messages.map((msg: Message) => ( <MessageView key={msg.id} message={msg} /> ))} </div> <div className='flex items-center px-2 mt-auto mb-2'> <input type='text' placeholder='Type your message...' className='flex-1 p-2 border border-gray-400 rounded outline-none bg-white' value={inputValue} onChange={(e) => setInputValue(e.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') { handlePublish(); } }} /> <button className='bg-blue-500 text-white px-4 ml-2 h-10 flex items-center justify-center rounded hover:bg-blue-600 transition-colors' onClick={handlePublish} > Publish </button> </div> </div> ); }
Copied!

Your application now supports publishing realtime messages! Type a message and click “Publish” to see it appear in your UI. Open another browser window 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 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 src/PresenceStatus.tsx with the following content:

React v2.9
// src/PresenceStatus.tsx // 'ably/react' exports hooks for working with presence on a channel import { usePresence, usePresenceListener } from 'ably/react'; 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 ( <div className='flex flex-col bg-white w-full h-full px-4 py-2'> <strong className='text-green-700 mr-4 text-center border-b border-gray-900'> Present: {presenceData.length} </strong> <div className='flex-1 flex-col flex flex-nowrap items-start gap-4 overflow-x-auto'> {presenceData.map((member, idx) => ( <div key={idx} className='flex items-center gap-1'> <span className='inline-block w-2 h-2 rounded-full bg-green-500' /> <span className='text-gray-800'> {member.clientId} {member.data?.status ? ` (${member.data.status})` : ''} </span> </div> ))} </div> </div> ); }
Copied!

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

React v2.9
// src/App.tsx // Existing imports import { PresenceStatus } from './PresenceStatus'; function App() { return ( <ChannelProvider channelName='my-first-channel'> <div className='flex flex-col w-[900px] h-full border-1 border-blue-500 rounded-lg overflow-hidden mx-auto font-sans'> <div className='flex flex-row w-full rounded-lg overflow-hidden mx-auto font-sans'> <div className='flex-1 bg-gray-100 text-center p-4'> <h2 className='text-lg font-semibold text-blue-500'> Ably Pub/Sub React </h2> <ConnectionState /> </div> </div> <div className='flex flex-1 flex-row justify-evenly'> <div className='flex flex-col w-1/4 border-r-1 border-blue-500 overflow-hidden mx-auto font-sans'> <div className='flex-1 overflow-y-auto'> {/* Your PresenceStatus component should go here */} <PresenceStatus /> </div> </div> <div className='flex flex-col bg-white w-3/4 rounded-lg overflow-hidden mx-auto font-sans'> <Messages /> </div> </div> </div> </ChannelProvider> ); } export default App;
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" --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 src/Messages.tsx file to include the new useEffect within your existing Messages component:

React v2.9
// src/Messages.tsx // Existing imports import { useEffect, useState } from 'react'; // MessageView function remains unchanged export function Messages() { // useStates, useChannel, and handlePublish function remain 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 }
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 page. 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...