IN THIS ARTICLE
Welcome to our 4-part tutorial on building a feature-rich chat application with React, a JavaScript library for building interactive UIs, and PubNub, providing APIs and infrastructure for building realtime applications.
Tutorial Overview
This post is Part One, and work your way through the entire tutorial. By the end, you’ll have a fully-functional React chat application powered by PubNub. You can navigate between parts with the links below or on the left sidebar.
- Part One: Sending and receiving messages
- Part Two: Storing message history and infinite scroll
- Part Three: Realtime user roster (ie. buddy list)
- Part Four: Typing indicators
Required Tools
Aside from React and PubNub, we’ll be using a number of other tools to build this
application. We’ll need node.js and its package manager npm, which we can use to install other packages, including:
- webpack – builds and bundles web application resources
- eslint – checks code for syntax errors and style rules
- mocha – test framework
- chai – an assertion library that works with mocha
- istanbul – measures how much code is tested
- babel – converts JavaScript into different dialects
This seems like a lot, but most of these are code quality tools, as well as two packages for bundling and compiling / minifying. That’s not strictly true though, since we’ll also be using some webpack plugins, as well as JavaScript libraries to actually program with.
Strictly speaking, we don’t actually need any of this. We could download React, ReactDOM, and PubNub’s SDK and write everything in a text editor. However, that approach is hard to grow and maintain.
For example, what happens when libraries update? How is bundling handled? What about minification? How can we make sure our software does what we think it does?
Fortunately, installing and maintaining JavaScript code and packages has become much easier over the last few years. To make things even easier, we’ll be using a robust starter repository that includes everything we need to start coding our chat application. This starter is based on the Rangle React/Redux starter project.
The React/Redux Starter
To get the starter, clone it with git:
git clone https://github.com/pubnub/pubnub-react.git git checkout tags/rev0
Using a command-line, navigate to the project: cd pubnub-react
. From there, several commands can be run.
- Install dependencies:
npm install
- Test things and get a coverage report:
npm run test
- Run a constantly updating development version:
npm run dev
Navigate a web browser to http://localhost:8080 to see a debug copy of your project. The window will refresh when your editor makes changes.
- Produce a distribution:
npm run build
This processes and optimizes your project resources for deployment, then builds a folder called dist in your project’s root folder.
React
React is a JavaScript view library that renders views on a variety of interfaces, like a web browser’s DOM. React is included in our starter project, so we don’t need to install it.
React lets us use JSX to write JavaScript code that looks like an HTML template. We could write all the code in plain JavaScript, but our starter has Babel, which lets us easily leverage JSX and ES6. ES2015 or ES6 is the latest version of the JavaScript. It brings a lot of new features such as: Classes, Arrow Functions, Modules, etc.
React will take our JSX code and render elements for us. Additionally, the JSX can be bound to data. Whenever, this data changes React will re-render the elements using the new version of the data. That’s all it does.
Our application is going to be sending and receiving chat messages — and doing a lot more in the future. We’ll need more code to link PubNub to React. In today’s post, we’ll start by binding PubNub directly to React; in future posts, we’ll deal with scalable state management.
PubNub
PubNub is a web service that provides a variety of realtime backend services. We’ll use it to do all the heavy lifting in our chat application. To start, we’ll use PubNub Realtime Messaging API.
This API will let our application subscribe to messages sent to a particular “channel”. We’ll also publish messages to a particular channel. To make this work, we’ll need to do three things:
- Initialize PubNub:
const PubNub = PUBNUB.init({ publish_key: 'your-secret-publish-key', subscribe_key: 'your-secret-subscribe-key', ssl: (location.protocol.toLowerCase() === 'https:'), // encrypt the channel });
- Subscribe to messages
this.PubNub.subscribe({ channel: 'ReactChat', message: function doSomething(message) { // do something with `message` } });
- Publish messages
this.PubNub.publish({ channel: 'ReactChat', message: 'Hello World', });
Interfacing React and PubNub
We’ve talked about several different concepts so far, including a broad introduction to building scalable applications with React. Now it’s time to put that knowledge to use by assembling our chat application. If you haven’t done so, checkout a copy of rev0.
rev0
gives us a complete framework for building a robust application. We already looked at the different tooling commands we can run. Let’s take a look at where we’re going to put our code.
The src
folder is split up as follows:
./src/ ├── actions - Redux actions (future) ├── components - Presentational components ├── constants - Project settings and constants ├── containers - Containers or smart components ├── index.html - Application entry point ├── index.js - JavaScript entry point ├── middleware - custom state middleware (future) ├── reducers - redux reducer functions (future) ├── store - state config (future) ├── styles - css/scss/less/other styles └── utils - utility functions/modules
There are many different places where we’ll be putting code. Determining what goes where is fairly straightforward.
How to Bootstrap Our App
Everything starts with index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>PubNub React Chat</title> </head> <body> <div id="root" /> </body> </html>
Ours is very simple. Webpack will take care of adding style and our code. The <div id="root"></div>
part is where we’ll insert our React application. Our application starts running in index.js:
import 'es5-shim'; import 'es6-shim'; import 'es6-promise'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import {App} from './containers/app'; // Global styles import './styles/index.css'; ReactDOM.render( <div></div>, document.getElementById('root') );
You’ll notice a few imports here. In ES6, the Import Statement allows us to import functions, objects or primitives that have been defined in an external module. The first few imports ensure that we have access to a number of modern browser features; then we import React and ReactDOM, followed by a container.
After that, there’s an import statement that references CSS. Webpack will use this to bring in our CSS. Finally, at the bottom is a render call with some data written in JSX. This is where React is first invoked.
Making sense of JSX
Looking at the JSX in the render method in index.js
we see that it looks like an HTML template. It’s not HTML though: it’s actually XML that gets compiled into JavaScript. The render function turns:
<div> <App /> </div>
into
ReactDOM.render(React.createElement( 'div', null, React.createElement(_app.App, null) ), document.getElementById('root'));
At first, it only looks a little bit easier to write the JSX. Once a component has any more than a few nested bits, however, it becomes much easier to deal with JSX than the pure JavaScript version. JSX looks like HTML but it has the power and scoping of JavaScript.
Emphasis on scoping because you’ll notice that Provider and Router are imported from libraries; they’re actually just JavaScript! The JSX convention is to Capitalize custom components.
Developers should expect that lowercase nodes like div will be converted to HTML elements.
Components and Containers
Components are essentially patches of screen real estate that are controlled by some code. Containers are components that are “smart” in that they know some things about how our application works. Here’s what our basic chat application is going to look like:
We can break this application into two components and one container. The container we’ll call App. Our App container will be responsible for all of the logic in our basic chat application. It will manage a props
(properties) object that gives data and functions to our views.
The App Container
Let’s make a container for our application in the containers/app.js
file, which will start off looking like:
import React from 'react'; class App extends React.Component { render() { return ( <div> <h1>Hello World!</h1> </div> ); } }
We’re starting with a basic “hello world” application shell. There’s a simple App
container component, which is an ES6 class that extends React.component
. It has a render()
method that returns some basic “hello world” JSX.
The ChatInput Component
Finally, we get to actually write some code.
- Create a folder in
src/
called components - Create a new file, called
ChatInput.js
insidesrc/components/
Components will always need the React library, so we’ll add that first:
import * as React from 'react';
Next, we declare a class for the component that extends the basic React component. Here, we are using the using the ES6 syntax for declaring a Class. Extending React.Component
provides us with needed things in our component like props, render()
, state, etc.
export default class ChatInput extends React.Component { render() { return <div>This is ChatInput</div>; } }
The only requirement is to define the render()
function. All other features of the React.Component
class are optional. The render function must return a single JSX node, and that node may contain child nodes. For now, an empty div will do.
Now we can connect this component to our container by editing src/containers/app
and adding two lines. At the top, we bring ChatInput
into scope by importing it:
import ChatInput from '../components/ChatInput';
Then later in app.js’s JSX, we’ll change it so that it uses our new component:
<div> <ChatInput /> </div>
Now is a good time to check if ChatInput is rendering correctly. This can be done by using npm run dev
from the root directory of your project, and navigating a browser to localhost:8080 where you should see this:
User Interface Using JSX
Realistically, we’re going to need to add some actual UI. In the src/components/ChatInput.js
file, replace the JSX in the render
method with this:
return (<footer className="teal"> <form className="container"> <div className="row"> <div className="input-field col s10"> <i className="prefix mdi-communication-chat" /> <input type="text" placeholder="Type your message" /> <span className="chip left"> <img src="//robohash.org/503483?set=set2&bgset=bg2&size=70x70" /> <span>Anonymous robot #503483</span> </span> </div> <div className="input-field col s2"> <button type="submit" className="waves-effect waves-light btn-floating btn-large"> <i className="mdi-content-send" /> </button> </div> </div> </form> </footer>);
The final component is all HTML nodes. One of the key things to note is that the HTML nodes are still JSX, and JSX compiles to JS and in JS class
is a reserved word. Consequently, className
is used in place of class
in the HTML nodes. Otherwise, this is pretty straightforward HTML. There is a hard-coded robot image we’ll swap out later with something dynamic.
CSS Definitions
The previous component made use of some CSS. We’ll need to define those styles somewhere. Let’s edit src/styles/index.css (remember back when the starter had imported it in our index.js for us?) and add the following:
footer { color: white; position: fixed; padding: 0; bottom: 0; left: 0; width: 100%; height: 130px; } footer .input-field input, button { border-color: #FFFFFF; } .chip { font-style: italic; }
We’re also going to use two third-party CSS styles. Normally, we’d fetch them in an npm module or some other library, but these ones are only available on CDN. Consequently, we’ll edit src/index.html
and add to the <head>
block:
<!-- Import Google Icon Font --> <link rel="stylsheet" href="//fonts.googleapis.com/icon?family=Material+Icons" /> <!-- Import materialize.css --> <link type="text/css" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/css/materialize.min.css" media="screen,projection" />
At this point the app should look something like this:
The ChatHistory Component
ChatHistory
is going to be created in a very similar fashion to ChatInput
. We’ll start by creating the src/components/ChatHistory.js file and importing React, then create/export a component:
import * as React from 'react'; export default class ChatHistory extends React.Component { render() { return <div>This is ChatHistory</div>; } }
Next, we’ll plug it into our container. First, we’ll add the ChatHistory
input to the top of app.js
, like so:
import ChatHistory from '../components/ChatHistory';
Then we’ll update app.js’s
render function:
render() { return ( <div> <ChatHistory /> <ChatInput /> </div> ); }
Like last time, now is a good time to check and see if everything works (if you’re not doing so in real time with npm run dev
).
More UI with JSX
We’ll add some more markup to the component including attribute data to give it a more well rounded look. In the src/components/ChatHistory.js
file, modify the render block to this:
return ( <ul className="collection"> <li className="collection-item avatar"> <img src="//robohash.org/107378?set=set2&bgset=bg2&size=70x70" alt="107378" className="circle" /> <span className="title">Anonymous robot #107378</span> <p> <i className="prefix mdi-action-alarm" /> <span className="message-date">05/19/2016 at 1:55PM</span> <br /> <span>Hello World!</span> </p> </li> </ul>);
We’ll need to update src/styles/index.css with:
/* ChatHistory styles */ .collection { margin-bottom:130px; margin-top: -1px; } .message-date { color: #585858; } body { background: #EEEEEE; }
At this point, we should have a complete “static view” of the application.
Bringing the Application to Life
Now on to the fun stuff! We’re going to make this dynamic using real data from our application.
Before diving into how to code this, we should think about the data structure and the needs of the application, so we know what to code.
In our application, we will have a chat history, which is a collection of messages. Each message contains information about who, when, and what. The ‘who’ is the name of the person who made the message, in our application it is always ‘Anonymous robot #’ followed by a random number. This random number is used as a unique identifier for the user and is only calculated once. The ‘when’ is a date/time stamp of when the message was sent. The ‘what’ is the message itself; a string of text.
So, all we need is:
- The current user’s unique ID (randomly generated)
- A collection of messages where each message contains:
- Who (ID)
- When (Date/Time)
- What (Message)
Adding Data
Before we can get data into our components, we need a place to store data. In future posts, we’ll formalize storing data (state). For now, we’ll just make a state object in src/containers/app.js
. Let’s start with two properties:
- a random userID to identify the chat participant
- an array to hold our message history
We’ll add state
as a property of our App class:
class App extends React.Component { state = { userID: Math.round(Math.random() * 1000000).toString(), history: [], }; ... }
We’ll also need a sendMessage
to the App class in order to respond to actions from ChatInput
:
sendMessage = (message) => { // for now this will let us know things work. `console` will give us a // warning though console.log('sendMessage', message); }
Now we can pass props into their respective component via JSX attributes: by updating the render method to look like this:
render(){ const { sendMessage, state } = this; return ( <div> <ChatHistory history={ state.history } /> <ChatInput userID={ state.userID } sendMessage={ sendMessage } /> </div> ); }
We’ll also tell React that ChatHistory
needs a history array. Add the following to the src/components/ChatHistory.js class:
export default class ChatHistory extends React.Component { static propTypes = { history: React.PropTypes.array, }; ... }
That’s all we need to do for the container. Next, we’ll update the components to use these new values passed into them.
ChatInput’s Data
Now we can update the render method of the ChatInput.js class file. First, we should create a props variable which will give us access to the HTML-like attributes we passed in previously.
const { props } = this;
We’ll also tell React what prop types to expect for this component:
static propTypes = { userID: React.PropTypes.string, sendMessage: React.PropTypes.func, };
There are two places in the JSX where you’ll see the number ‘503483’: the <img/>
tag and the sibling <span>
near the middle of the JSX being returned by the render function.
We’re going to replace the numbers with our userID from props. When you want to use a variable in JSX, you need to wrap it in curly braces {}. This is fine for the span tag, but curly braces inside the middle of the string won’t be parsed. For this, we’ll need to create a string for the URL and pass that variable in using curly braces. At the top of the render function, just below the props declaration, add this:
const imgURL = '//robohash.org/' + props.userID + '?set=set2&bgset=bg2&size=70x70';
This is just taking the URL we had on the <img> src
and replacing the number ‘503483’ with our props.userID
.
Now we can replace the two lines for the <img> and <span> with this:
<img src={ imgURL } /> <span>Anonymous robot #{ props.userID }</span>
When you look at the rendered source in your browser’s console, you’ll see something like this:
<span class="chip left"> <img src="//robohash.org/587329?set=set2&bgset=bg2&size=70x70"> <span> <!-- react-text: 21 --> Anonymous robot # <!-- /react-text --> <!-- react-text: 22 --> 587329 <!-- /react-text --> </span> </span>
So now whenever you reload the application, you’ll notice the number changes, that’s because we’re generating a new random number when the app is mounted (loaded).
Next, we’ll hook up the sendMessage
property. To do this, we’ll create a function that is called when the form is submitted. Inside this function, we’ll do three things:
- Check if the user typed anything, if not then exit
- Send the message via
sendMessage
- Clear the input field for the next message and set focus
Since we’ll need to access the text in the form to get the message contents and to reset the field, we’ll add a React ref to the input. Update the JSX to add a new attribute: ref=”txtMessage”
and it should look like this:
<input ref="txtMessage" type="text" placeholder="Type your message" />
This ‘ref’ name allows us to access this JSX object via this.refs.txtMessage
, assuming ‘this’ belongs to ChatInput.
Let’s create an onSubmit method in our class that we’ll connect to the form’s onSubmit property. To do this, add the following property to ChatInput:
onSubmit = (e) => { e.preventDefault(); };
Then we need to update the render function to connect this. First, we’ll update the destructuring of this to include onSubmit…
const { props, onSubmit } = this;
…and update the <form>
to add onSubmit={ onSubmit }
so it looks like this:
<form className="container" onSubmit={ onSubmit }>
Now, when you click on the submit button (the arrow), it will invoke our onSubmit method and it will not reload the page because of preventDefault()
being called, and that’s how we know our onSubmit
is working.
Next we can update the onSubmit
to do the three things we wanted it to do. First, we’ll create a variable to hold the message’s contents, and exit the function if the length is zero. Update the onSubmit with these lines, below the preventDefault()
:
// Check if the message is empty const message = this.refs.txtMessage.value; if (message.length === 0) { return; }
Notice that we used .value
because txtMessage
is actually a DOM node, and the way we access the input’s value from a DOM node is by its value property.
Next, we’ll add code to call sendMessage
, but first we need to construct a message object to use in ChatHistory
. We know the chat history will need to display three pieces of information: Who, What, and When.
Inside onSubmit
, we know the current user’s ID via the props, so that covers the Who. The What is the message content itself, which we have in the message variable. The When is the current date and time; for this, we can use new Date()
. To keep things simple, we don’t need to store the whole Date
object, just the number that represents its value. You can use .valueOf()
to obtain the number of milliseconds since Jan 1, 1970 (epoch time). From this number, we can easily reconstruct a date object by passing it into its constructor, which we’ll do in ChatHistory.
With this knowledge, we can add the following code to the onSubmit function in ChatInput.js:
// Build a message object and send it const messageObj = { Who: this.props.userID, What: message, When: new Date().valueOf(), }; this.props.sendMessage(messageObj);
Finally, we clear the input field and set focus on it using the DOM object:
// Clear the input field and set focus this.refs.txtMessage.value = ''; this.refs.txtMessage.focus();
Since we’re going to set focus after we clear the field, it makes sense that we should set focus initially so the user doesn’t have to manually set focus on the field. To do this, we can add a single line of code to our componentDidMount
for ChatInput:
componentDidMount() { this.refs.txtMessage.focus(); }
That’s it for the chat input. Now it’s time to update the chat history so we can see the messages we’ve been sending.
ChatHistory’s Data
The ChatHistory component will take in an array of message objects and display them. We will start by updating the render function. First, add a props variable at the top like this:
const { props } = this; // same as `const props = this.props;`
Now we’ll loop over each message object and build a list item for each one. We’ll be using Array’s map()
.
To do this, we’ll use the curly braces to output the result of the array map, since we’ll be returning JSX in our array map.Wrap the <li>
so that it looks like so:
return (<ul className="collection"> { props.history.map((messageObj) => { return ( <li className="collection-item avatar"> <!-- keep the contents here --> </li> ); }) } </ul>);
What this does is returns a copy of our list item for each messageObj
found in the history. Next, we’ll change that content to be dynamic based on the message object’s content.
Before we add the content, it’s important to add a ‘key’ attribute to the list item. Whenever you repeat values in JSX, React needs a unique identifier so that it can apply updates as needed to that DOM element. Since our messages have a timestamp down to the millisecond, this should be adequate.
Update the <li>
to include this new attribute…
key={ messageObj.When }
…so it looks like this:
<li className="collection-item avatar" key={ messageObj.When }>
Setting the UserID should be familiar since it’s like what we did for the ChatInput. First, we’ll build a URL string by combining the img src and userID. Add this line above the return statement in our map:
const imgURL = '//robohash.org/' + messageObj.Who + '?set=set2&bgset=bg2&size=70x70';
Notice that we used messageObj.Who instead of userID.
In the ChatHistory.js
render function we will update the JSX to use dynamic data. Update the <img>
to replace the src with imgURL, and update the ‘alt’ attribute to use the messageObj.Who
, and we’ll also update the following span
tag to set the correct number:
<img src={ imgURL } alt={ messageObj.Who } className="circle" /> <span className="title">Anonymous robot #{ messageObj.Who }</span>
Next, we’ll drop in the the What in place of the ‘Hello World!’ text near the bottom:
<span>{ messageObj.What }</span>
Finally, we add the When. For this, we’ll need to do three things:
- Create a date object from the epoch time in messageObj.When
- Format the date into a nice string
- Put the formatted string into the JSX
Just below the imgURL declaration, add the following lines of code:
const messageDate = new Date(messageObj.When); const messageDateTime = messageDate.toLocaleDateString() + ' at ' + messageDate.toLocaleTimeString();
Next, you can update the JSX to use the value messageDateTime
. Look for the span tag with the class name message-date
update it like so:
<span className="message-date">{ messageDateTime }</span>
That’s it for ChatHistory!
Now’s a good time to play with the application to see if it works. Next up, we’ll integrate with PubNub! If the app doesn’t work as you expected you can always compare your code to the final version available on GitHub.
Connecting Chat to the Internet
At this point, we have everything in place to send and receive messages, and also to build on in an organized fashion.
Right now, our application only allows us to chat with people connected to our host, which is probably just your computer. We are going to connect our application to the PubNub network so that we can have our application share messages with everyone else using this application. This means two different people on two different computers using the same code on their own servers will be able to share messages.
There are four things we must do to make this work:
- Include the PubNub javascript API:
//cdn.pubnub.com/pubnub-3.14.5.min.js
- Call PubNub’s
init()
when the application starts - Call PubNub’s
publish()
when sending a message - Use
subscribe()
to listen for messages from other users
To include the API, we’ll add a script tag to src/index.html
at the bottom of src/index.html’s
body tag:
<script src="//cdn.pubnub.com/pubnub-3.14.5.min.js"></script>
Before we call PubNub’s init function we need a place to put it. React features life cycle methods that we can hook into. Let’s create a life cycle method that will trigger when a component is added to the DOM. Add the following method to the App class:
componentDidMount() { }
Next, we’ll call the init()
function. Add the following lines to the componentDidMount
lifecycle hook:
this.PubNub = PUBNUB.init({ publish_key: 'Enter-your-pub-key', subscribe_key: 'Enter-your-sub-key', ssl: (location.protocol.toLowerCase() === 'https:'), });
The publish and subscribe keys here are used for this application only. Your own apps should use your own keys; see PubNub’s website for details. Once PubNub is initialized, we can then use the object it returned to publish and subscribe.
The next thing to do is start listening for messages, by subscribing. When we get a message from the PubNub Network, we can add it to our history. Add the following to the end of the lifecycle hook:
this.PubNub.subscribe({ channel: 'ReactChat', message: (message) => this.setState({ history: this.state.history.concat(message) }), });
The channel is any string you want it to be, just make sure you’re publishing on the same channel you’re subscribing to. The object message
contains our Who, What, and When. When a message comes back from the PubNub network, it will call the function that we set in message
, passing the messageObj
into the function.
Finally, we’re ready to publish messages. To do this, we’ll replace our console log with a call to PubNub.publish
:
sendMessage = (message) => { this.PubNub.publish({ channel: 'ReactChat', message: message, }); }
As you can see, this looks very similar to subscribe()
. The only difference is message
here isn’t a callback function, instead it’s the data you want to send across the PubNub network. In our case, the message we’re sending is our message object containing Who, What, and When.
That’s it! We can now send and receive messages via PubNub.
It’s time to move onto Part Two, where we’ll implement message history and infinite scroll,
We will walk through refactoring this app to use Redux & ImmutableJS for state management. Also, we’ll be integrating PubNub’s History API, allowing us to store previously sent messages, and allow users to scroll through and see a complete chat history.