Tutorials

React Realtime Commenting Tutorial

In this tutorial, we will see how to use Ably Realtime client library to build a realtime commenting system with React. We’ll be making use Ably’s public channel. Once a comment is made, we’ll publish it to the public channel. We will also be subscribed to the channel so we can see comments as they are added in realtime.

This tutorial will not include any server-side implementation. Hence, comments won’t be persisted to any storage.

React is a JavaScript library for building user interfaces. It is component-based and declarative which makes it easy to build complex UI with it. For more information on React, please refer to their official website https://reactjs.org.

React logo

Step 1 – Set up a free account with Ably

In order to run these tutorials locally, you will need an Ably API key. If you are not already signed up, you should 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 and store it so that you can use it later in this tutorial

    Copy API Key screenshot

Step 2 – Create a React App

We’ll start by creating a new React app. To do this, we’ll be using create-react-app. create-react-app allows you to create a React app without having to worry about build configurations. To use it, you’ll need to install create-react-app locally if you don’t have it already:

npm install -g create-react-app

Once installed, we can use it to create a new React app. We’ll call it reactjs-realtime-commenting:

create-react-app reactjs-realtime-commenting

The new React app is ready. We can start it by running:

cd reactjs-realtime-commenting
npm start

This builds the static site, runs a web server and opens a browser. If your browser does not open, navigate to http://localhost:3000.

See this step in Github

Step 3 – Delete Unused Files

Before we move on to building upon this app, let’s quickly delete some files we won’t be needing. Run the command below to do so:

rm src/App.css src/App.test.js src/index.css src/logo.svg

See this step in Github

Step 4 – Create Component Folder

Now, we’ll create a new folder within the src folder called components. This folder will contain all our React components.

mkdir src/components

Once that is created, we need to move App.js into the components folder:

mv src/App.js src/components

See this step in Github

Step 5 – Remove References To Unused Files

Since we have deleted and moved some files which are still being referenced in src/index.js and src/App.js, we need to update these files to not reference the deleted and moved files. Firstly, update src/index.js as below:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import registerServiceWorker from './registerServiceWorker';

ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();

Next, update src/components/App.js as below:

import React, { Component } from 'react';

class App extends Component {
  render() {
    return (
      <div className="App">

      </div>
    );
  }
}

export default App;

See this step in Github

Step 6 – Add Bulma CSS Framework

We’ll be using Bulma CSS framework so as not to waste time writing CSS. We’ll reference it from CDN (Content Delivery Network). Add the line below to the <head> section of public/index.html:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.0/css/bulma.min.css">

See this step in Github

Step 7 – Install Ably

To start using Ably you first need to install the NPM module. The NPM module can be installed as follows:

npm install ably --save

The client library must be instanced with the API key you copied in Step 1. Note in production we recommend you always use the token authentication scheme for browser clients, however in this example we use an API key for simplicity. We’ll create a dedicated file for this and instantiate a global Ably instance so we can easily use it in multiple places. Within src folder, create a new file named ably.js and add the code below into it:

import { Realtime } from 'ably/browser/static/ably-commonjs.js';

window.Ably = new Realtime('INSERT-YOUR-API-KEY-HERE');

Next, let’s reference the file created above in src/index.js:

require('./ably');

See this step in Github

Step 8 – Create CommentBox Component

Within the components folder, create a new file named CommentBox.js and add the code below into it:

import React, { Component } from 'react';

class CommentBox extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div>
        <h1 className="title">Kindly leave your thoughts below</h1>
        <form onSubmit={this.addComment}>
          <div className="field">
            <div className="control">
              <input type="text" className="input" name="name" placeholder="Your name"/>
            </div>
          </div>
          <div className="field">
            <div className="control">
              <textarea className="textarea" name="comment" placeholder="Add a comment"></textarea>
            </div>
          </div>
          <div className="field">
            <div className="control">
              <button className="button is-primary">Submit</button>
            </div>
          </div>
        </form>
      </div>
    );
  }
}

export default CommentBox;

This component renders a comment form. Once the form is submitted, we trigger an onSubmit event which in turn calls addComment(). The form won’t do anything for now as we are yet to create addComment().

See this step in Github

Step 9 – Adding and Publishing Comment

Next, we’ll create addComment(). Still within components/CommentBox.js, add the code below just before render():

addComment(e) {
  // Prevent the default behaviour of form submit
  e.preventDefault();

  // Get the value of the comment box
  // and make sure it not some empty strings
  const comment = e.target.elements.comment.value.trim();
  const name = e.target.elements.name.value.trim();

  // Make sure name and comment boxes are filled
  if (name && comment) {
    const commentObject = { name, comment };

    this.props.handleAddComment(commentObject);

    // Publish comment
    /*global Ably*/
    const channel = Ably.channels.get('comments');
    channel.publish('add_comment', commentObject, err => {
      if (err) {
        console.log('Unable to publish message; err = ' + err.message);
      }
    });

    // Clear input fields
    e.target.elements.comment.value = '';
    e.target.elements.name.value = '';
  }
}

We first prevent the default form submission behaviour (that is, reloading the page). We then get the form inputs entered (name and comment) and make sure they are not just some empty strings. If the inputs are properly filled, we add the comment just made to the array of comments. Then we publish the comment just made to a comments channel with an add_comment event. Adding /*global Ably*/ tells ESlint that we are using the global variable (Ably) we defined earlier. With this, we won’t get the Eslint error: 'Ably' is not defined no-undef. Lastly, we clear the form fields.

Next, let’s bind addComment() to the this keyword by adding this line to the constructor() just after super():

this.addComment = this.addComment.bind(this);

See this step in Github

Step 10 – Create Comment Component

Within the components folder, create a new file named Comment.js and add the code below into it:

import React, { Component } from 'react';

class Comment extends Component {
  render() {
    return (
      <article className="media">
        <figure className="media-left">
          <p className="image is-64x64">
           <img src="https://bulma.io/images/placeholders/128x128.png" alt="Avatar" />
          </p>
        </figure>
        <div className="media-content">
          <div className="content">
            <p>
              <strong>{this.props.comment.name}</strong>
              <br />
              {this.props.comment.comment}
            </p>
          </div>
        </div>
      </article>
    );
  }
}

export default Comment;

This component renders a single comment. It accepts the comment as props. Props are custom attributes that are used to pass input data to components.

See this step in Github

Step 11 – Create Comments Component

Within the components folder, create a new file named Comments.js and add the code below into it:

import React, { Component } from 'react';
import Comment from './Comment';

class Comments extends Component {
  render() {
    return (
      <section className="section">
        {
          this.props.comments.map((comment, index) => {
            return <Comment key={index} comment={comment} />
          })
        }
      </section>
    );
  }
}

export default Comments;

This component accepts a comments props and renders the Comment component for each of the comments available. It passes the actual comment to the Comment component as props.

See this step in Github

Step 12 – Create App Component

The App component will serve as the parent component. This means the App component will contain other components which will be nested within it. Open components/App.js and replace it content with the code below:

import React, { Component } from 'react';
import CommentBox from './CommentBox';
import Comments from './Comments';

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      comments: []
    }
  }

  render() {
    return (
      <section className="section">
        <div className="container">
          <div className="columns">
            <div className="column is-half is-offset-one-quarter">
              <CommentBox />
              <Comments comments={this.state.comments} />
            </div>
          </div>
        </div>
      </section>
    );
  }
}

export default App;

This contains the components we created earlier. We define a comments state which is an array of comments. It defaults to an empty array. This will be updated as we add comment through the comment form. Also, we pass the comments state as props to the Comments component. This is how the Comments component get the comments it renders.

See this step in Github

Step 13 – Updating State With Newly Added Comment

Recall from step 9 above, we called handleAddComment() which is responsible for adding a new comment to state. But we are yet to create this function, let’s do that now. Within components/App.js, add the code below just before render():

handleAddComment(comment) {
  this.setState(prevState => {
    return {
      comments: prevState.comments.concat(comment)
    };
  });
}

The code above adds the comment that was made to state. This way, the comments list is updated with new comments in realtime.

Next, still within components/App.js, let’s bind handleAddComment() to the this keyword by adding this line to the constructor() just after super():

this.handleAddComment = this.handleAddComment.bind(this);

Also, still within components/App.js, pass handleAddComment as props to CommentBox component as below:

<CommentBox handleAddComment={this.handleAddComment} />

See this step in Github

Step 14 – Configure all channels within a namespace to persist messages to disk

We’ll be using Ably’s history feature to display our comments in realtime. Before we can make use of the history feature, we need to first configure our channel to persist messages to disk. Channels can be named using any unicode string characters with the only restriction that they cannot start with a [ or : character as these are used as “special” characters. A colon : is used to delimit channel namespaces in a format such as namespace:channel. Namespaces provide a flexible means to group channels together. Channel grouping can be useful when, for example, configuring permissions (capabilities) for a set of channels within a client token or setting up rules to apply to one or more channels.

We will be using channel rules in this tutorial to ensure all channels in the persisted namespace are configured to persist messages to disk i.e. we will explicitly enable the history feature. Follow these steps:

  1. Visit your account dashboard, navigate to the same app you chose in Step 1 when obtaining your API key earlier
  2. Click on the Settings tab and scroll down to the “Channel rules” section
  3. Click the “Add new rule” button (see below)

    Add new channel rule screenshot
  4. Once the modal has opened, enter “persisted” for the namespace, check the Persisted check box to enable history, and click the “Create channel rule” button (see below)

    Create channel rule screenshot

You have now enabled history for all channels in the persisted namespace i.e. any channel with a name matching the pattern persisted:* will store published messages to disk.

Step 15 – Displaying Comments

We have seen how we can publish newly added comments. Now, we need to display those comments being published in realtime. We’ll do this with a React lifecycle componentDidMount() hook. Still within components/App.js, add the code below just before handleAddComment():

componentDidMount() {
  /*global Ably*/
  const channel = Ably.channels.get('comments');

  channel.attach();
    channel.once('attached', () => {
      channel.history((err, page) => {
        // create a new array with comments only in an reversed order (i.e old to new)
        const comments = Array.from(page.items, item => item.data)

        this.setState({ comments });

        channel.subscribe((msg) => {
          const commentObject = msg.data;
          this.handleAddComment(commentObject);
        })
      });
    });
}

The componentDidMount() hook runs after the component output has been rendered to the DOM. It is a good place to fetch our comments from the comments history. We connect to the comments channel and listen for the attached event. Then we update the comments state with the comment pulled from history.

Once we’ve obtained the history of the channel, we then subscribe to new messages on the channel. Whenever a new message is sent on the channel, we will add a new comment using handleAddComment.

See this step in Github


Final output screenshot

That’s it. We now have a realtime commenting system. To test it out, start the app:

npm start

Then open http://localhost:3000 in two different browser tabs. Add a comment in one of the opened tabs and watch the other tab update with the comment in realtime.

Download tutorial source code

The complete source code for each step of this tutorial is available on Github.

We recommend that you clone the repo locally:

git clone https://github.com/ably/tutorials.git

Checkout the tutorial branch:

git checkout reactjs-realtime-commenting

Install NPM dependencies. Be sure to switch into project’s directory and then run this command in your terminal:

npm install

And then run the app locally by adding your Ably API key to src/ably.js and run

npm start

to start the web server and open the browser.

Next steps

1. If you would like to find out more about how channels, publishing and subscribing works, see the Realtime channels & messages documentation
2. Learn more about Ably features by stepping through our other Ably tutorials
3. Learn more about Ably’s history feature
4. Gain a good technical overview of how the Ably realtime platform works
5. Get in touch if you need help