Alexey Korepanov
by Alexey Korepanov
2 min read

Categories

Tags

I was recently looking for a sample how to use PubNub with custom React hooks and I didn’t find any complete example. Some of them were missing important details which I had to figure out later by myself. So here is a brief tutorial.

First let’s create a PubNubHooks.js file with an empty context, provider and a custom hook:

import React, { createContext, useEffect } from 'react';

const PubNubContext = createContext();

export const usePubNub = () => {
  const context = React.useContext(PubNubContext);
  if (context === undefined) {
    throw new Error('`usePubNub` hook must be used within a `PubNubContextProvider` component');
  }
  return context;
};

export const PubNubContextProvider = (props) => {
  const { children } = props;
  return (
    <PubNubContext.Provider value={ {} }>
      {children}
    </PubNubContext.Provider>
  );
};

For now this context passes an empty object to the child components and the hook returns that empty object.

Now let’s add PubNub logic to the hook. We want the hook to return messages per room and a send function to post a message to the room:

import PubNub from 'pubnub';

...

export const PubNubContextProvider = (props) => {
  const { children } = props;

  const pubnub = new PubNub({
    publishKey: 'your key here',
    subscribeKey: 'your key here',
  });

  // messages is a map, each element is a list of messages
  // in the room, the key is the room name.
  const [messages, setMessages] = useState({});

  // Let's say useRooms is another custom webhook which provides a list of
  // current rooms.
  // rooms could be something like ['room1', 'room2', 'room3'];
  const { rooms } = useRooms();

  const subscribeChannels = () => {
    pubnub.subscribe({
      channels: rooms,
    });

    const listener = {
      message(msg) {
        const room = msg.subscribedChannel;
        const { message } = msg;

        // Add message to the state
        setMessages((oldMessages) => {
          ...oldMessages,
          [room]: [
            ...(oldMessages[room] ? oldMessages[room] : []),
            message,
          ],
        });
      },
    };
    pubnub.addListener(listener);
    return listener;
  };

  const unsubscribePubNubChannels = (listener) => {
    pubnub.removeListener(listener);
    pubnub.unsubscribe({
      channels: rooms,
    });
  };

  // Re-subscribe to PubNub on every update
  useEffect(() => {
    const listener = subscribeChannels();
    return () => {
      unsubscribeChannels(listener);
    };
  });

  const send = (room, message) => {
    const publishConfig = {
      channel: room,
      message,
    };
    pubnub.publish(publishConfig, (status, response) => {
      // You can handle errors here
    });
  }

  return (
    <PubNubContext.Provider value={ {
      messages,
      send,
    } }
    >
      {children}
    </PubNubContext.Provider>
  );
};

Please note that pubnub.addListener should be called only once. Messages from all channels will be passed to this listener function.

Now let’s add the provider to the app to make the hook available to all React components in the tree:

import { PubNubContextProvider } from 'PubNubHooks';

const App = () => (
  <PubNubContextProvider>
    <RoomMessages roomName="some room name"/>
  </PubNubContextProvider>
)

Let’s create a component to show the messages for the specific room and sending a message to that room:

import { usePubNub } from 'PubNubHooks';
...

const RoomMessages = ({ roomName }) => {
  const { messages, send } = usePubNub();
  const roomMessages = messages[roomName]
  return (
    <div>
      {
        roomMessages.map((message) => (
          <div>{message.text}</div>
        ))
      }
      <button onClick={() => send(roomName, 'Hi there');>
        Send message
      </button>
    </div>
  )
}