Making React realtime with websockets
A basic understanding of React and Node.js are needed to follow this tutorial.
Every chat application requires real time functionality. This amongst other benefits allows interaction between users in different location(s). A chat application like this can be built to function on diverse platforms, be it mobile or web applications.
In this tutorial, I will be showing you how to build a chat application using React and Pusher. This is a very simple application and it comes with functionality which will be showing some of the rich features of Pusher and how you can easily combine it with a modern library like React.
Pusher is a platform that allows developers to easily build an application with realtime features as quickly as possible. Pusher specialises in building realtime and scalable infrastructures for developers and is packaged with powerful features like client events, queryable API, Pub/Sub messaging and others.
We will explore Pusher as we proceed in this tutorial and leverage on the DOM manipulation, event driven and data-binding ability of React.
A basic knowledge of JavaScript and React will be of advantage so as to effectively participate in this tutorial.
A sneak peek into what we will build in this tutorial:
Getting Started
For a hitch-free flow of procedures in this tutorial, we will begin by setting up the required credentials with Pusher. If you don’t have an account, kindly create one . Once you are done, go ahead and create a new app from your dashboard. Don’t forget to take note of your app_id
, key
, secret
and cluster
as you will be required to use them later in this tutorial.
Create React-app
To quickly scaffold a React app, we will make use of the create-react-app
tool. This gives us easy access to the CLI tool that will be used to start building our chat application.
It is important that you have Node and npm installed on your machine. Quickly follow this link to complete that, if you don’t have it already. To verify if you have Node and npm installed, open up the terminal and run the command below:
npm -v
node -v
The version for each one will be displayed, if they are installed. The latest versions are fine.
Installation
Now, install create-react-app and also scaffold a new React app with the following commands:
npm install -g create-react-app
create-react-app react-pusher
Once all the necessary files are installed, change directory into react-pusher
and start the application with:
npm start
By now, you should have a new tab opened in your default browser:
We have successfully set up the front-end of the application, we will return to this very soon. Let’s proceed to setup the server application.
Node Server
Node.js using the Express web framework will be used as our server application. The server application, amongst other functions, will provide endpoints to send messages for our chat app so as to ensure interaction between users. There are number of dependencies required for the application server, so let’s install them immediately:
npm install --save axios body-parser cors express pusher pusher-js
To configure the entry point of the application, create a file called server.js
and paste the code below into it:
const Pusher = require('pusher');
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());
const pusher = new Pusher({
appId: 'APP_ID',
key: 'APP_KEY',
secret: 'APP_SECRET',
cluster: 'APP_CLUSTER',
encrypted: true
});
app.set('PORT', process.env.PORT || 5000);
app.post('/message', (req, res) => {
const payload = req.body;
pusher.trigger('chat', 'message', payload);
res.send(payload)
});
app.listen(app.get('PORT'), () =>
console.log('Listening at ' + app.get('PORT')))
Here we initialised Pusher with the required credentials and then created an endpoint to process messages from the frontend of our application.
Building Components
To define a good application structure and fully see our chat app in action, lets create some components. In React, components can either be stateful or stateless depending on the functionality that it was created for. The common patter is to have state for container components only and pass those states to UI components as props.
Create components from the terminal with:
touch ChatList.js ChatBox.js
and lastly their respective stylesheet:
touch ChatList.css ChatBox.css
Each of the components’ logic need to be created. Lets do that now
ChatBox.js
This component contains the input field for accepting the users message and also displays a welcome message showing the username passed in from the parent component. Furthermore, handleTextChange
is also passed from the parent component. This component is exported and configured like:
import React from "react";
import './ChatBox.css';
export default ({ text, username, handleTextChange }) => (
<div>
<div className="row">
<div className="col-xs-12">
<div className="chat">
<div className="col-xs-5 col-xs-offset-3">
<input
type="text"
value={text}
placeholder="chat here..."
className="form-control"
onChange={handleTextChange}
onKeyDown={handleTextChange}
/>
</div>
<div className="clearfix"></div>
</div>
</div>
<h4 className="greetings">Hello, {username}</h4>
</div>
</div>
);
This component above is styled with imported stylesheet ChatBox.css
:
.greetings {
margin-top: 90px;
text-align: center;
}
ChatList.js
This is responsible for displaying the list of chats inputted by each user. Added to it is the username and messages. This is also implemented as shown below and exported:
import React from "react";
import "./ChatList.css";
import avatar from "./avatar.png";
export default ({ chats }) => (
<ul>
{chats.map(chat => {
return (
<div>
<div className="row show-grid">
<div className="col-xs-12">
<div className="chatMessage">
<div key={chat.id} className="box">
<p>
<strong>{chat.username}</strong>
</p>
<p>{chat.message}</p>
</div>
<div className="imageHolder">
<img src={avatar} className="img-responsive avatar" alt="logo" />
</div>
</div>
</div>
</div>
</div>
);
})}
</ul>
);
And the stylesheet ChatList.css
:
ul {
list-style-type: none;
}
.chat {
margin-top: 50px;
}
.chatMessage {
position: relative;
border: 1px solid #ccc;
border-radius: 5px;
margin: 20px auto;
width: 700px;
display: table;
}
.box {
background-color: #ffffff;
padding: 20px;
border-bottom: 1px solid #cccccc;
}
.imageHolder {
width: 50px;
height: 50px;
position: absolute;
top: 20px;
right: 50px;
}
We have successfully completed the ChatBox
and ChatList
component. As far as the application is concerned, these modules are not yet available to be rendered in the parent app component yet. To correct this, the App.js
file needs to be configured.
Parent Container Component (App.js)
This is regarded as the parent component. It ensures effective parent-child communication by passing props down to each child component. In App.js, a class named App is used to extend the React Component class and a constructor is defined with its properties and methods. In App.js required node modules and CSS file are imported with:
import React, { Component } from 'react';
import axios from 'axios';
import Pusher from 'pusher-js';
import ChatList from './ChatList';
import ChatBox from './ChatBox';
import logo from './logo.svg';
import './App.css';
...
Next a class that extends the Component class imported from the React module is created. Also defined in this class is the state variables. The super()
method is also called whenever a child class is extended from a parent class. In this case, props is also passed as a parameter. The state variables in the app are created and defined in the constructor like this:
...
class App extends Component {
constructor(props) {
super(props);
this.state = {
text: '',
username: '',
chats: []
};
}
componentDidMount() {
...
}
handleTextChange(e) {
...
}
render() {
return (
<div className="App">
...
</div>
);
}
}
...
After defining the required state of the app, you can start subscribing to the Pusher channel and binding to the events emitted by your server:
componentDidMount() {
const username = window.prompt('Username: ', 'Anonymous');
this.setState({ username });
const pusher = new Pusher('APP_KEY', {
cluster: 'APP_CLUSTER',
encrypted: true
});
const channel = pusher.subscribe('chat');
channel.bind('message', data => {
this.setState({ chats: [...this.state.chats, data], test: '' });
});
this.handleTextChange = this.handleTextChange.bind(this);
}
handleTextChange(e) {
if (e.keyCode === 13) {
const payload = {
username: this.state.username,
message: this.state.text
};
axios.post('http://localhost:5000/message', payload);
} else {
this.setState({ text: e.target.value });
}
}
Bringing it all together:
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React-Pusher Chat</h1>
</header>
<section>
<ChatList chats={this.state.chats} />
<ChatBox
text={this.state.text}
username={this.state.username}
handleTextChange={this.handleTextChange}
/>
</section>
</div>
);
}
In case you missed anything, you can find the complete App.js file right here.
Conclusion
This article has covered the basic steps necessary to create a realtime chat application using React and Pusher. Moving forward, the concept demonstrated here can be built upon and extended to cater for any other realtime features you wish to build in your application. I hope you have found this tutorial helpful and feel free to improve on the code available here and drop comment(s) below, if any.
15 January 2018
by Christian Nwamba