• Blog:

  • Home
  • Ably News
  • Ably Engineering
  • Developers
  • Industry Trends
  • Hardest Aspects of Realtime Engineering
  • Ably + React Hooks NPM package now out

    By: Jo Franchetti 7 min read

    Whether React is your favorite JavaScript framework or not, it is the most commonly used web framework. If you're building single-page apps, high fidelity frontends, or even in some cases static sites, there's a high chance that you're using React to do it.

    In the past it was sometimes difficult to decide on the best place to put code from libraries, such as ably-js, which can cause re-renders, and which have their own state management. Functional components and React Hooks make this choice much simpler. We’ve built an Ably React Hooks npm package to reduce your mental load and make using Ably with React a breeze.

    State management in React

    Traditionally React works by binding components to the state that is passed between components using "props". Props are arguments passed to the constructor of a React component. React components act like JavaScript functions in that they accept props and return a React element to be rendered. This "prop passing" pattern is often complemented by a state management library like Redux to centrally control the application’s state. In addition, applications will frequently include "stateful" components which create, maintain and update their own state - often by making API requests to capture the initial state and reflecting any changes back to the API they sourced the data from.

    Stateful components are usually written by implementing logic using React’s componentDidMount() method. This allows us to execute code when the component is rendered. It works well for simple HTTP calls, but when combined with code that implements its own state management, it can cause problems.

    Problems occurring when using componentDidMount and Ably

    One of the most common questions we get from people using Ably’s JavaScript library (ably-js) and React is where they should be managing the lifecycle of the Ably client instance. They often find that by doing what seems like the obvious technique - creating a client, and subscribing to messages in componentDidMount - they end up rapidly exhausting connection limits and reaching their message quotas. This is because ably-js maintains its own connections and subscriptions list, and subscribing every time the component mounts will add an additional subscription. Even worse, if the React UI is changing and re-rendered based on Ably messages (which it usually is), it is possible to end up in a recursive situation where every time a message is received, the component is re-rendered and creates an additional subscription, and exponentially increases Ably connection usage.

    Ably DevRel and Customer Support spend a lot of time providing guidance to developers to make sure that their ably-js client instances are managed correctly, and that channels are unsubscribed from during componentDidUnmount. This React method allows us to run code when a component is removed (which happens before re-rendering it on change) while the ably-js client instance itself is long-lived. We even have a GitHub repository of example React components to provide examples of this exact pattern and have produced base components in the past to make this easier.

    React functional components to the rescue...?

    In 2019 (since the release of version 16.8), React introduced the concept of functional components (in contrast to class-based, or "classical" components). At a glance, functional components are simpler than classical components and they remove the need for componentDidMount and componentDidUnmount in favor of preferring React Hooks for managing state.

    Functional components are usually preferable in newer codebases because of their visual simplicity and closer correlation to the functional programming ideals that developers often advocate. However, making stateful components from functional components is a little trickier and involves using the React useState, useEffect and useMemo hooks to produce similar results as putting code in componentDidMount and componentDidUnmount. Unfortunately, this means that the confusion of where to create instances of the Ably client, and when to subscribe and unsubscribe from channels is perhaps amplified by functional components.

    Enter custom React Hooks

    To solve this confusion we've built a couple of custom React Hooks for using Ably in React functional components. We’ve also released them as an npm package so that you can import them directly into your React projects.

    They provide a bulletproof pattern for using the Ably client. They allow you to get a callback when messages are received by your component, they re-render and re-subscribe if the channel you're subscribing to is modified, and they make presence events trivial to consume so that your React app can be aware of other connected clients.

    A sequence diagram of subscribing to a channel with Ably and the React Hooks package
    A sequence diagram of subscribing to a channel with Ably and the React Hooks package
    A sequence diagram of publishing a message with Ably and the React Hooks package
    A sequence diagram of publishing a message with Ably and the React Hooks package
    A sequence diagram of receiving a message from Ably using the React Hooks package
    A sequence diagram of receiving a message from Ably using the React Hooks package

    How do I use the npm package?

    Installation

    These React Hooks ship as an ES6 module, so you can use the import syntax in your react code:

    npm install --save @ably-labs/react-hooks

    This works out of the box using create-react-app - so you can use the package immediately.

    Ably channels and API keys

    In order to use these hooks, you will need an Ably API key. If you are not already signed up, you can sign up now for a free Ably account. Once you have an Ably account:

    1. Log into your app dashboard.
    2. Under “Your apps”, click on “Manage app” for any app you wish to use for this tutorial, or create a new one with the “Create New App” button.
    3. Click on the “API Keys” tab.
    4. Copy the secret “API Key” value from your Root key, we will use this later when we build our app.

    We strongly recommend that you use Token Authentication, to authenticate with the Ably service. In the examples below we use an API key directly in the markup, this is for local development only and should not be used for production code and should not be committed to your repositories.

    Usage

    Once you've added the package to your project with npm, you can use the hooks in your React code. Start by adding a reference to the hooks:

    import { configureAbly, useChannel } from "@ably-labs/react-hooks";
    

    Then you need to use the configureAbly function to create an instance of the Ably JavaScript SDK.

    configureAbly({ key: "your-ably-api-key", clientId: generateRandomId() });
    

    configureAbly matches the method signature of the Ably SDK - and requires either a string or an AblyClientOptions. You can use this configuration object to set up your API keys, or tokenAuthentication as you normally would. If you want to use the usePresence hook, you'll need to provide a clientId explicitly.

    Once you've done this, you can use the hooks in your code. The simplest example is as follows:

    const [channel] = useChannel("your-channel-name", (message) => {
        console.log(message);
    });
    

    Every time a message is sent to your-channel-name it'll be logged to the console. You can do whatever you need to with those messages.

    useChannel()

    The useChannel hook lets you subscribe to a channel and receive messages from it:

    const [channel, ably] = useChannel("your-channel-name", (message) => {
        console.log(message);
    });
    

    Both the channel instance, and the Ably JavaScript SDK instance are returned from the useChannel call.

    useChannel really shines when combined with a regular react useState hook - for example, you could keep a list of messages in your app state, and use the useChannel hook to subscribe to a channel, and update the state when new messages arrive.

    const [messages, updateMessages] = useState([]);
    
    const [channel] = useChannel("your-channel-name", (message) => {
        updateMessages((prev) => [...prev, message]);
    });
    
    // Convert the messages to list items to render in a react component
    const messagePreviews = messages.map((msg, index) => {
        <li key={index}>{msg.data.someProperty}</li>
    });
    

    useChannel supports all of the parameter combinations of a regular call to channel.subscribe, which means that you can filter the messages you have subscribed to by providing a message type to the useChannel function:

    const [channel] = useChannel("your-channel-name", "test-message", (message) => {
        console.log(message); 
        // Only logs messages sent using the `test-message` message type
    });
    

    The channel instance returned by useChannel can be used to send messages to the channel. It's just a regular Ably JavaScript SDK channel instance.

    channel.publish("test-message", { text: "message text" });
    

    usePresence()

    The usePresence hook lets you subscribe to presence events on a channel - this will notify you when a user joins or leaves the channel.

    const [presenceData, updateStatus] = usePresence("your-channel-name");
    
    // Convert presence data to list items to render
    const peers = presenceData.map((msg, index) => {
        <li key={index}>{msg.clientId}: {msg.data}</li>
    });
    

    usePresence returns an array of presence messages (each message is a regular Ably JavaScript SDK presenceMessage instance). You can optionally provide a string when you use usePresence to set an initial presence data string.

    const [presenceData, updateStatus] = usePresence("your-channel-name", "initial state");
    

    The updateStatus function can be used to update the presence data for the current client:

    updateStatus("new status");
    

    The new state will be sent to the channel, and any other clients subscribed to the channel are notified of the change immediately.

    See it in use

    We’ve used this package recently in a demo chat application. This fully featured, scalable chat application is built with React, you can see an example of the useChannel hook inside the chat component. Stay tuned for more content about the fully featured scalable chat app 😉

    Let us know

    We hope that these hooks make building React apps with Ably realtime elements a breeze. If you have any questions or would like to see any additional features please contact us on [email protected] or @ablyrealtime on Twitter.