Getting started: Chat with React

This guide will get you started with Ably Chat on a new React application built with Vite.

It will take you through the following steps:

  • Creating a client and establishing a realtime connection to Ably
  • Creating a room and subscribing to its messages
  • Sending messages to the room and editing messages
  • Retrieving historical messages to provide context for new joiners
  • Setting up typing indicators to see which clients are typing
  • Use presence to display the online status of users in the room
  • Subscribing to and sending reactions
  • Disconnecting and resource cleanup

Prerequisites

Ably

  1. Sign up for an Ably account

  2. Create a new app and get your API key. You can use the root API key that is provided by default to get started.

  3. Install the Ably CLI:

npm install -g @ably/cli
  1. 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

Create a new project

  1. Create a new React + TypeScript project using Vite. For detailed instructions, refer to the Vite documentation.
npm create vite@latest my-chat-react-app -- --template react-ts
  1. Setup Tailwind CSS for styling the application. Ensure you import tailwind in your local App.CSS file and add it to your vite.config.ts file. For installation instructions, see the Tailwind CSS documentation for Vite.
npm install tailwindcss @tailwindcss/vite
  1. Install the Ably Chat SDK, this will also install the Ably Pub/Sub SDK as a dependency:
npm install @ably/chat

Adding required imports

You will need the following imports in your src/App.tsx file, these will be used at various points in the guide:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

// App.tsx
import React, {useCallback, useEffect, useState} from 'react';
import "./App.css";
import {
    ChatRoomProvider,
    useChatConnection,
    useMessages,
    usePresence,
    usePresenceListener, useRoom, useRoomReactions,
    useTyping
} from '@ably/chat/react';
import {
    ChatMessageEvent,
    Message,
    ChatMessageEventType,
    RoomReaction
} from '@ably/chat';

Step 1: Setting up the Ably and Chat client providers

The Ably Pub/Sub SDK and the Ably Chat SDK expose React hooks and context providers to make it easier to use them in your React components. The AblyProvider and ChatClientProvider should be used at the top level of your application, typically in main.tsx. These are required when working with the useChatConnection hook and ChatRoomProvider exposed by Ably Chat.

Replace the contents of your src/main.tsx file with the following code to set up the providers:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

// main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import * as Ably from 'ably';
import { ChatClient, LogLevel } from '@ably/chat';
import { ChatClientProvider } from '@ably/chat/react';
import { AblyProvider } from 'ably/react';
import App from './App'; // Your main app component

// Create your Ably Realtime client and ChatClient instances:
const realtimeClient = new Ably.Realtime({
  key: '{{API_KEY}}',
  clientId: 'my-first-client',
});

const chatClient = new ChatClient(realtimeClient, {
  logLevel: LogLevel.Info,
});

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <AblyProvider client={realtimeClient}>
      <ChatClientProvider client={chatClient}>
        <App /> {/* Your main app component */}
      </ChatClientProvider>
    </AblyProvider>
  </React.StrictMode>,
);

Step 2: Connect to Ably

Clients establish a connection with Ably when they instantiate an SDK. This enables them to send and receive messages in realtime across channels. This hook must be nested within a ChatClientProvider.

In your project, open src/App.tsx, and add the following functions along with the imports from the previous step:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

// App.tsx
// This component will display the current connection status
function ConnectionStatus() {
  // Hook to get the current connection status
  const { currentStatus } = useChatConnection();
  return (
    <div className="p-4 text-center h-full border-gray-300 bg-gray-100">
      <h2 className="text-lg font-semibold text-blue-500">Ably Chat Connection</h2>
      <p className="mt-2">Connection: {currentStatus}!</p>
    </div>
  );
}

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 border-1 border-blue-500 rounded-lg overflow-hidden mx-auto font-sans">
        <div className="flex-1 border-1 border-blue-500">
          <ConnectionStatus/>
        </div>
      </div>
    </div>
  );
}

export default App;

Run your application by starting the development server:

npm run dev

Open your browser to localhost:5173, and you will see the connection status reflected in the UI: "Currently connected!".

Step 3: Create a room

Now that you have a connection to Ably, you can create a room. Use rooms to separate and organize clients and messages into different topics, or 'chat rooms'. Rooms are the entry object into Chat, providing access to all of its features, such as messages, presence and reactions.

Ably Chat exposes the ChatRoomProvider to help you create and manage rooms. It must be nested under the ChatClientProvider described above.

This provider also gives you access to the room via the useRoom() hook, which you can be used to interact with the room and monitor its status.

In your project, open src/App.tsx, and add a new component called RoomStatus:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

// App.tsx
function RoomStatus() {
  // This component will display the current room status
  const [currentRoomStatus, setCurrentRoomStatus] = useState('');
  const {roomName} = useRoom({
    onStatusChange: (status) => {
      setCurrentRoomStatus(status.current); // Update the room status
    },
  });
  return (
    <div className="p-4 text-center h-full border-gray-300 bg-gray-100">
      <h2 className="text-lg font-semibold text-blue-500">Room Status</h2>
      <p className="mt-2">Status: {currentRoomStatus}
        <br/>
        Room: {roomName}
      </p>
    </div>
  );
}

Update your main app component to include the ChatRoomProvider and nest the RoomStatus component inside it:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

// App.tsx
function App() {
  // Wrap your Room component with the ChatRoomProvider:
  return (
    <ChatRoomProvider
      name="my-first-room" // The room name you want to create or join
      release={true} // Release the room automatically when unmounted
      attach={true} // Attach to the room automatically when mounted
    >
      <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 border-1 border-blue-500 rounded-lg overflow-hidden mx-auto font-sans">
          <div className="flex-1 border-1 border-blue-500">
            <ConnectionStatus/>
          </div>
          <div className="flex-1 border-1 border-blue-500">
            {/* Your RoomStatus component should go here */}
            <RoomStatus/>
          </div>
        </div>
      </div>
    </ChatRoomProvider>
  );
}

export default App;

The above code creates a room with the name my-first-room and sets up a listener to monitor the room status. It also displays the room name and current status in the UI.

Step 4: Send a message

Messages are how your clients interact with one another and Ably Chat exposes a useMessages() hook to interact with the messages feature of the Chat SDK. After the room provider is set up, you can use the useMessages() hook to send and receive messages in the room.

In your project, open src/App.tsx, and add a new component called ChatBox, like so:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

// App.tsx
function ChatBox() {
  const [inputValue, setInputValue] = useState('');
  // State to hold the messages
  const [messages, setMessages] = useState<Message[]>([]);

  // The useMessages hook subscribes to messages in the room and provides a send method
  const { send } = useMessages({
    listener: (event: ChatMessageEvent) => {
      const message = event.message;
      switch (event.type) {
        case ChatMessageEventType.Created: {
          // Add the new message to the list
          setMessages((prevMessages) => [...prevMessages, message]);
          break;
        }
        default: {
          console.error('Unhandled event', event);
        }
      }
    }
  });

  // Function to handle sending messages
  const handleSend = () => {
    if (!inputValue.trim()) return;
    send({ text: inputValue.trim() }).catch((err) =>
      console.error('Error sending message', err))
    setInputValue('');
  };

  return (
  <div className="flex flex-col w-full h-[600px] item-left border-1 border-blue-500 rounded-lg overflow-hidden mx-auto font-sans">
    <div className="flex-1 p-4 overflow-y-auto space-y-2">
      {messages.map((msg: Message) => {
        const isMine = msg.clientId === 'my-first-client';
        return (
          <div key={msg.serial} className={`max-w-[60%] rounded-2xl px-3 py-2 shadow-sm ${
            isMine ? 'bg-green-200 text-gray-800 rounded-br-none' : 'bg-blue-50 text-gray-800 rounded-bl-none'
          }`}>
            {msg.text}
          </div>
        );
      })}
    </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') {
            handleSend();
          }
        }}
      />
      <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={handleSend}
      >
        Send
      </button>
    </div>
  </div>
  );
}

Add the ChatBox component to your main app component:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

// App.tsx
function App() {
  return (
    <ChatRoomProvider
      name="my-first-room" // The room name you want to create or join
      release={true} // Release the room automatically when unmounted
      attach={true} // Attach to the room automatically when mounted
    >
      <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 border-1 border-blue-500 rounded-lg overflow-hidden mx-auto font-sans">
          <div className="flex-1 border-1 border-blue-500">
            <ConnectionStatus/>
          </div>
          <div className="flex-1 border-1 border-blue-500">
            <RoomStatus/>
          </div>
        </div>
        <div className="flex flex-1 flex-row flex justify-evenly">
          <div className="flex flex-col bg-white w-1/2 border-1 border-blue-500 rounded-lg overflow-hidden mx-auto font-sans">
          {/* Your ChatBox component should go here */}
            <ChatBox/>
          </div>
        </div>
      </div>
    </ChatRoomProvider>
  );
}

The UI will automatically render the new component, and you will be able to send messages to the room.

Type a message in the input box and click the send button. You'll see the message appear in the chat box.

You can also use the Ably CLI to send a message to the room from another environment:

  ably rooms messages send my-first-room 'Hello from CLI!'

You'll see the message in your app's chat box UI. If you have sent a message via CLI, it should appear in a different color to the one you sent from the app.

Step 5: Edit a message

If your client makes a typo, or needs to update their original message then they can edit it. To do this, you can extend the functionality of the ChatBox component to allow updating of messages. The useMessages() hook exposes the update() method of the Chat SDK messages feature.

Expose the update() method from the useMessages() hook and then add a method to the ChatBox component to handle the edit action like so:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

// App.tsx - Place this in the ChatBox component
const onUpdateMessage = useCallback(
  (message: Message) => {
    const newText = prompt('Enter new text');
    if (!newText) {
      return;
    }
    update(
      message.serial,
      {
        text: newText,
        metadata: message.metadata,
        headers: message.headers,
      },
    ).catch((error: unknown) => {
      console.warn('Failed to update message', error);
    });
  },
  [update],
);

Update the rendering of messages in the chat box to enable the update action in the UI:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

// App.tsx - Update the rendering of messages in the ChatBox component
return (
  <div className="flex flex-col w-full h-[600px] item-left border-1 border-blue-500 rounded-lg overflow-hidden mx-auto font-sans">
    <div className="flex-1 p-4 overflow-y-auto space-y-2">
      {messages.map((msg: Message, idx: number) => {
        const isMine = msg.clientId === 'my-first-client';
        return (
          <div
            /* message update handling */
            key={idx}
            className={`flex ${isMine ? 'justify-end' : 'justify-start'}`}
            onClick={() => onUpdateMessage(msg)}
          >
            <div
              className={`max-w-[60%] rounded-2xl px-3 py-2 shadow-sm ${
                isMine
                  ? 'bg-green-200 text-gray-800 rounded-br-none'
                  : 'bg-blue-50 text-gray-800 rounded-bl-none'
              }`}
            >
              {msg.text}
            </div>
          </div>
        );
      })}
    </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') {
            handleSend();
          }
        }}
      />
      <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={handleSend}
      >
        Send
      </button>
    </div>
  </div>
);

Update the listener provided to the useMessages() hook to handle the ChatMessageEventType.Updated event:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

// App.tsx - Replace the useMessages hook with the following:
const {send, update} = useMessages({
  listener: (event: ChatMessageEvent) => {
    const message = event.message;
    switch (event.type) {
      case ChatMessageEventType.Created: {
        // Add the new message to the list
        setMessages((prevMessages) => [...prevMessages, event.message]);
        break;
      }
      case ChatMessageEventType.Updated: {
        setMessages((prevMessages) => {
          // Find the index of the message to update
          const index = prevMessages.findIndex((other) => message.isSameAs(other));

          // If the message is not found, return the previous messages
          if (index === -1) {
            return prevMessages;
          }

          // Apply the update to the original message
          const newMessage = prevMessages[index].with(event);

          // Create a new array with the updated message and return it
          const updatedArray = prevMessages.slice();
          updatedArray[index] = newMessage;
          return updatedArray;
        });
        break;
      }
      default: {
        console.error('Unhandled event', event);
      }
    }
  }
});

Now, when you click on a previously sent message in the UI, it will prompt you to enter new text. After entering the required change and submitting, it will send the updated message to the room, where the listener will receive it and update the UI accordingly.

Step 6: Message history and continuity

Ably Chat enables you to retrieve previously sent messages in a room. This is useful for providing conversational context when a user first joins a room, or when they subsequently rejoin it later on. The useMessages() hook exposes the historyBeforeSubscribe() method to enable this functionality. This method returns a paginated response, which can be queried further to retrieve the next set of messages. To do this, you need to expose historyBeforeSubscribe() on the hook, and extend the ChatBox component to include a method to retrieve the last 10 messages when the component mounts.

In your src/App.tsx file, add the following useEffect to your ChatBox component:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// App.tsx
function ChatBox() {
  /* Expose historyBeforeSubscribe() */
  /* const {send, update, historyBeforeSubscribe} = useMessages({...*/
  /* existing code */
  useEffect(() => {
    async function loadHistory() {
      try {
        if (!historyBeforeSubscribe) {
          // The room is not ready yet
          return;
        }
        // Retrieve the last 10 messages
        const history = await historyBeforeSubscribe({ limit: 10 });
        // Set the messages in the state
        setMessages(history.items);
      } catch (error) {
        console.error('Error loading message history:', error);
      }
    }
    loadHistory();
  }, [historyBeforeSubscribe]);
  /* rest of your code */
}

The above code will retrieve the last 10 messages when the component mounts, and set them in the state.

Try the following to test this feature:

  1. Use the ably CLI to simulate sending some messages to the room from another client.
  2. Refresh the page, this will cause the ChatBox component to mount again and call the historyBeforeSubscribe() method.
  3. You'll see the last 10 messages appear in the chat box.

Step 7: Show who is typing a message

Typing indicators enable you to display messages to clients when someone is currently typing. An event is emitted when someone starts typing, when they press a keystroke, and then another event is emitted after a configurable amount of time has passed without a key press.

The Chat SDK exposes the useTyping() hook to enable this feature. The currentlyTyping array from the hook tells you which clients are currently typing, allowing you to render them in the UI. The hook also exposes keystroke() and stop() methods to start and stop typing.

In your src/App.tsx file, update the existing ChatBox component. It should include the typing indicator hook, a function to handle text input and a modification to the existing handleSend method so typing stops on message send:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

// App.tsx
function ChatBox() {
  /* existing code */

  // Expose the currentlyTyping set and the keystroke and stop methods
  const { currentlyTyping, keystroke, stop } = useTyping();

  /* replace the existing handleSend method with the following */
  const handleSend = () => {
    if (!inputValue.trim()) return;
    send({text: inputValue.trim()}).catch((err) =>
      console.error('Error sending message', err))
    setInputValue('');

    /* stop typing when the message is sent */
    stop().catch((err) => console.error('Error stopping typing', err))
  };

  /* add the following method to handle input changes */
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const newValue = e.target.value;
    setInputValue(newValue);
    if (newValue.trim().length > 0) {
      // If the input value is not empty, start typing
      keystroke().catch(
        (err) => console.error('Error starting typing', err))
    } else {
      // If the input is cleared, stop typing
      stop().catch(
        (err) => console.error('Error stopping typing', err))
    }
  };

  /* rest of your code */

To render the typing indicator, update ChatBox rendering section like so:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

// App.tsx - ChatBox component
return (
  <div className="flex flex-col w-full h-[600px] item-left border-1 border-blue-500 rounded-lg overflow-hidden mx-auto font-sans">
    <div className="flex-1 p-4 overflow-y-auto space-y-2">
      {messages.map((msg: Message, idx: number) => {
        const isMine = msg.clientId === 'my-first-client';
        return (
          <div
            key={idx}
            className={`flex ${isMine ? 'justify-end' : 'justify-start'}`}
            onClick={() => onUpdateMessage(msg)}
          >
            {/* message update handling */}
            <div
              className={`max-w-[60%] rounded-2xl px-3 py-2 shadow-sm ${
                isMine
                  ? 'bg-green-200 text-gray-800 rounded-br-none'
                  : 'bg-blue-50 text-gray-800 rounded-bl-none'
              }`}
            >
              {msg.text}
            </div>
          </div>
        );
      })}
    </div>
    <div
      className="flex flex-col border-t border-gray-300 bg-gray-100"
      style={{ minHeight: '100px', maxHeight: '100px' }}
    >
      {/* Typing indicator */}
      <div className="h-6 px-2 pt-2">
        {currentlyTyping.size > 0 && (
          <p className="text-sm text-gray-700 overflow-hidden">
            {Array.from(currentlyTyping).join(', ')}
            {' '}
            {currentlyTyping.size > 1 ? 'are' : 'is'} typing...
          </p>
        )}
      </div>
      {/* Text input & message sending */}
      <div className="flex items-center px-2 mt-auto mb-2">
        <input
          type="text"
          placeholder="Type something..."
          className="flex-1 p-2 border border-gray-400 rounded outline-none bg-white"
          value={inputValue}
          onChange={handleChange}
          onKeyDown={(event) => {
            if (event.key === 'Enter') {
              handleSend();
            }
          }}
        />
        <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={handleSend}
        >
          Send
        </button>
      </div>
    </div>
  </div>
);

When you start typing in the input box, your client will be indicated as typing, and if you clear all text, the indicator will stop. Other connected clients can also be displayed in the list if they're typing, you can use the Ably CLI to simulate typing from another client by running the following command:

  ably rooms typing keystroke my-first-room --client-id "my-cli"

Step 8: Display who is present in the room

Display the online status of clients using the presence feature. This enables clients to be aware of one another if they are present in the same room. You can then show clients who else is online, provide a custom status update for each, and notify the room when someone enters it, or leaves it, such as by going offline.

The Chat SDK exposes both the usePresence() and usePresenceListener() hooks to interact with the presence feature. The usePresence() hook allows you to enter the room and update your presence status, while the usePresenceListener() hook allows you to subscribe to presence updates for the room. The usePresenceListener() hook also returns an object with the presenceData array, which contains the current presence data for the room.

In your src/App.tsx file, create a new component called PresenceStatus like so:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

// App.tsx
function PresenceStatus() {
  // The usePresence hook enters the current client into the room presence
  usePresence();
  // The usePresenceListener hook subscribes to presence updates for the room
  const { presenceData } = usePresenceListener();
  return (
    <div className="flex flex-col border-b border-gray-300 bg-white w-full h-full px-4 py-2">
      <strong className="text-green-700 mr-4 text-center border-b border-gray-900">
        Online: {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}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

Add the PresenceStatus component to your main app component like so:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

// App.tsx
function App() {
  return (
  <ChatRoomProvider
    name="my-first-room" // The room name you want to create or join
    release={true} // Release the room automatically when unmounted
    attach={true} // Attach to the room automatically when mounted
  >
    <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 border-1 border-blue-500 rounded-lg overflow-hidden mx-auto font-sans">
        <div className="flex-1 border-1 border-blue-500">
          <ConnectionStatus />
        </div>
        <div className="flex-1 border-1 border-blue-500">
          <RoomStatus />
        </div>
      </div>
      <div className="flex flex-1 flex-row justify-evenly">
        <div className="flex flex-col w-1/2 border-1 border-blue-500 rounded-lg overflow-hidden mx-auto font-sans">
          <div className="flex-1 border-1 border-blue-500 overflow-y-auto">
            {/* Your PresenceStatus component should go here */}
            <PresenceStatus />
          </div>
        </div>
        <div className="flex flex-col bg-white w-1/2 border-1 border-blue-500 rounded-lg overflow-hidden mx-auto font-sans">
          <ChatBox />
        </div>
      </div>
    </div>
  </ChatRoomProvider>
);
}

You'll now see your current client ID in the list of present users.

You can also use the Ably CLI to enter the room from another client by running the following command:

  ably rooms presence enter my-first-room --client-id "my-cli"

Step 9: Send a room reaction

Clients can send a reaction to a room to show their sentiment for what is happening, such as a point being scored in a sports game. Ably Chat provides a useReactions() hook to send and receive reactions in a room. These are short-lived (ephemeral) and are not stored in the room history.

In your src/App.tsx file, add a new component called ReactionComponent, like so:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

// App.tsx
function ReactionComponent() {
  const reactions = ['👍', '❤️', '💥', '🚀', '👎', '💔'];
  const [roomReactions, setRoomReactions] = useState<RoomReaction[]>([]);
  const { send } = useRoomReactions({
    listener: (reactionEvent) => {
      setRoomReactions([...roomReactions, reactionEvent.reaction]);
    },
  });

  return (
    <div>
      {/* Reactions buttons */}
      <div className="flex justify-evenly items-center px-4 py-2 border-t border-gray-300 bg-white mx-auto">
        {reactions.map((reaction) => (
          <button
            key={reaction}
            onClick={() =>
              send({ name: reaction }).catch((err) =>
                console.error('Error sending reaction', err)
              )
            }
            className="text-xl p-1 border border-blue-500 rounded hover:bg-blue-100 text-blue-500 transition-colors"
          >
            {reaction}
          </button>
        ))}
      </div>

      {/* Received reactions */}
      <div className="flex gap-2 px-2 py-2 border-t border-gray-300 mx-auto">
        <span>Received reactions:</span>
        <div className="flex-1 flex items-center max-h-[24px] gap-1 overflow-x-auto whitespace-nowrap scrollbar-thin scrollbar-thumb-gray-300">
          {roomReactions.map((r, idx) => (
            <span
              key={idx}
              className="px-2 py-1 bg-white rounded text-blue-600"
            >
              {r.name}
            </span>
          ))}
        </div>
      </div>
    </div>
  );
}

Add the ReactionComponent component to your main app component:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

// App.tsx
function App() {
  // Wrap your Room component with the ChatRoomProvider:
  return (
    <ChatRoomProvider
      name="my-first-room" // The room name you want to create or join
      release={true} // Release the room automatically when unmounted
      attach={true} // Attach to the room automatically when mounted
    >
      <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 border-1 border-blue-500 rounded-lg overflow-hidden mx-auto font-sans">
          <div className="flex-1 border-1 border-blue-500">
            <ConnectionStatus />
          </div>
          <div className="flex-1 border-1 border-blue-500">
            <RoomStatus />
          </div>
        </div>
        <div className="flex flex-1 flex-row justify-evenly">
          <div className="flex flex-col w-1/2 border-1 border-blue-500 rounded-lg overflow-hidden mx-auto font-sans">
            <div className="flex-1 border-1 border-blue-500 overflow-y-auto">
              <PresenceStatus />
            </div>
            <div className="flex-1 border-1 min-h-[100px] max-h-[100px] border-blue-500">
              {/* Your ReactionComponent component should go here */}
              <ReactionComponent />
            </div>
          </div>
          <div className="flex flex-col bg-white w-1/2 border-1 border-blue-500 rounded-lg overflow-hidden mx-auto font-sans">
            <ChatBox />
          </div>
        </div>
      </div>
    </ChatRoomProvider>
  );
}

The above code should display a list of reactions that can be sent to the room. When you click on a reaction, it will send it to the room and display it in the UI.

You can also send a reaction to the room via the Ably CLI by running the following command:

  ably rooms reactions send my-first-room 👍

Step 10: Disconnection and release

When you're done with a room or your application is unmounting, it's important to properly clean up resources to prevent memory leaks and unnecessary network usage.

Automatic detachment on unmount

For React components, when a component using the ChatRoomProvider unmounts, the room will automatically detach from the underlying channel and clean up associated resources. This behavior is controlled by the release prop on the ChatRoomProvider:

React

1

2

3

4

5

6

7

8

// App.tsx
<ChatRoomProvider
  name="my-first-room"
  release={true} // Release the room automatically when unmounted
  attach={true}
>
  {/* Your components */}
</ChatRoomProvider>

1

2

3

4

5

6

7

8

9

10

import { useChatClient } from '@ably/chat/react';

const { chatClient } = useChatClient();

const handleReleaseRoom = () => {
  chatClient.rooms.release("my-first-room")
    .catch(err => console.error("Error releasing room", err));
};

// Call handleReleaseRoom when needed

Closing the realtime connection

It's important to note that no unmount process exists for disconnecting the Ably realtime connection. If you need to do this, you can call realtimeClient.connection.close() directly:

React

1

2

3

4

5

6

7

8

9

10

11

12

import * as Ably from 'ably';

const realtimeClient = new Ably.Realtime({
  key: '{{API_KEY}}',
  clientId: 'my-first-client',
});

const handleDisconnect = () => {
  realtimeClient.connection.close();
};

// Call handleDisconnect when needed

This will close the connection to Ably and clean up any associated resources.

Next steps

Continue exploring Ably Chat with React:

Read more about the concepts covered in this guide:

Explore the Ably CLI further, or check out the Chat JS API references for additional functionality.

Select...