Tutorial: Build a React.js Chat App
Adding chat functionality to your web app can be crucial for enabling easy communication between your users. Whether you have an online marketplace, social network or a collaboration tool, chat lets your users connect in a flexible and personal way, without having to leave the app.
In this tutorial we're going to build a React.js chat application using Scaledrone, a tool that makes realtime communication really easy. The app will work similarly to apps like Messenger, WhatsApp or LINE.
This app will also be cross-platform compatible with our iOS and Android chat tutorials!
Setting Up
We'll start by creating a new React app using create-react-app
, an official tool for getting started with React projects.
In order to use create-react-app
you need Node.js. Follow the instructions on the link to install if you haven't already. Once you've done that, creating our starter app is really easy. Open Terminal and navigate to a folder where you'd like your app to exist. Type in the following command:
npx create-react-app app-name
cd app-name
Note: The
app-name
can be anything you like.
Open the newly created app-name
folder in your favorite editor.
Next, we'll need to include the CSS to make sure our app looks nice. Replace the contents of App.css with the contents of this file.
Now we're ready to get started!
Showing Messages
To make our chat app we'll use three components.
- An
App
component which will manage sending and receiving messages and rendering the inner components. - A
Messages
component which will be a list of messages. - An
Input
component with a text field and button so that we can send our messages.
create-react-app
has already made an App
component for you which we'll modify later. Let's start with the Messages
component.
Create a new file in your src folder called Messages.js. Create a new component in this file called Messages
, and add a render
function like in the following code:
import {Component} from "react";
import React from "react";
class Messages extends Component {
render() {
const {messages} = this.props;
return (
<ul className="Messages-list">
{messages.map(m => this.renderMessage(m))}
</ul>
);
}
}
export default Messages;
The component will receive the messages it should display as a prop from the App
component. We'll render a list containing each message. The list items themselves will contain the sender's name and avatar, as well as the text of the message. Add the following function to the component:
renderMessage(message) {
const {member, text} = message;
const {currentMember} = this.props;
const messageFromMe = member.id === currentMember.id;
const className = messageFromMe ?
"Messages-message currentMember" : "Messages-message";
return (
<li className={className}>
<span
className="avatar"
style={{backgroundColor: member.clientData.color}}
/>
<div className="Message-content">
<div className="username">
{member.clientData.username}
</div>
<div className="text">{text}</div>
</div>
</li>
);
}
This will render the JSX for each individual message.
Now that we have the Messages
component, we need to make sure the App
component actually renders it. Head over to App.js and add an import of the Messages
component to the top of the file:
import Messages from "./Messages";
I mentioned before that we'll show usernames and avatars in our app. Usually you would have a login screen where you would get this information, but for the purposes of this tutorial we'll generate random names and colors for the avatar. Add the following top-level functions to the top of the file:
function randomName() {
const adjectives = ["autumn", "hidden", "bitter", "misty", "silent", "empty", "dry", "dark", "summer", "icy", "delicate", "quiet", "white", "cool", "spring", "winter", "patient", "twilight", "dawn", "crimson", "wispy", "weathered", "blue", "billowing", "broken", "cold", "damp", "falling", "frosty", "green", "long", "late", "lingering", "bold", "little", "morning", "muddy", "old", "red", "rough", "still", "small", "sparkling", "throbbing", "shy", "wandering", "withered", "wild", "black", "young", "holy", "solitary", "fragrant", "aged", "snowy", "proud", "floral", "restless", "divine", "polished", "ancient", "purple", "lively", "nameless"];
const nouns = ["waterfall", "river", "breeze", "moon", "rain", "wind", "sea", "morning", "snow", "lake", "sunset", "pine", "shadow", "leaf", "dawn", "glitter", "forest", "hill", "cloud", "meadow", "sun", "glade", "bird", "brook", "butterfly", "bush", "dew", "dust", "field", "fire", "flower", "firefly", "feather", "grass", "haze", "mountain", "night", "pond", "darkness", "snowflake", "silence", "sound", "sky", "shape", "surf", "thunder", "violet", "water", "wildflower", "wave", "water", "resonance", "sun", "wood", "dream", "cherry", "tree", "fog", "frost", "voice", "paper", "frog", "smoke", "star"];
const adjective = adjectives[Math.floor(Math.random() * adjectives.length)];
const noun = nouns[Math.floor(Math.random() * nouns.length)];
return adjective + noun;
}
function randomColor() {
return '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16);
}
The usernames are generated by adding a random adjective to a noun, leading to cool-sounding names like "hiddensnow" or "throbbingfrog". The colors are generated by generating a random hex number, while the member's ID is generated by generating a random string.
Now that we have those functions, we can use them to set our initial state in the App
component. Add the following to the top of App
:
state = {
messages: [
{
text: "This is a test message!",
member: {
color: "blue",
username: "bluemoon"
}
}
],
member: {
username: randomName(),
color: randomColor()
}
}
Our initial state will be one test image and a randomly generated member.
Next, update the contents of the render
method to the following:
render() {
return (
<div className="App">
<Messages
messages={this.state.messages}
currentMember={this.state.member}
/>
</div>
);
}
Run the app by navigating to the project folder in Terminal and typing in:
npm start
Head over to localhost:3000 and take a look at the first message in your chat app!

Adding Input
Now that we have a way to display our messages, we need a way to type them! Create a new file in the src folder called Input.js. In this file, add a new component called Input
:
import {Component} from "react";
import React from "react";
class Input extends Component {
state = {
text: ""
}
}
export default Input;
This component will have a text field to enter the message and a button to send it. We'll keep track of the currently entered text in our state
. Add the following render
method to the component:
render() {
return (
<div className="Input">
<form onSubmit={e => this.onSubmit(e)}>
<input
onChange={e => this.onChange(e)}
value={this.state.text}
type="text"
placeholder="Enter your message and press ENTER"
autofocus="true"
/>
<button>Send</button>
</form>
</div>
);
}
Whenever the textfield's value changes, we'll update our state to match. Add the following function to the class:
onChange(e) {
this.setState({text: e.target.value});
}
This will trigger a render whenever the text field changes. Finally, we need to handle sending the message. Add the following function to the class:
onSubmit(e) {
e.preventDefault();
this.setState({text: ""});
this.props.onSendMessage(this.state.text);
}
To send the message we'll use a callback inside the component's props which we'll receive from App
. We need to prevent the default behavior so our app doesn't refresh every time a new message is sent. We'll also update the state so that we clear the textfield.
Just like we did with Messages
, we need to make sure App
will render Input
. Head over to App.js and add the import for your new component to the top of the file:
import Input from "./Input"
Next, update the contents of the render
method to show the Input
component:
render() {
return (
<div className="App">
<Messages
messages={this.state.messages}
currentMember={this.state.member}
/>
<Input
onSendMessage={this.onSendMessage}
/>
</div>
);
}
Finally, we need to handle sending the message. Add the following function to App
:
onSendMessage = (message) => {
const messages = this.state.messages
messages.push({
text: message,
member: this.state.member
})
this.setState({messages: messages})
}
For now we'll just add it onto the messages array in our state
. If you head back to your web browser you should see an input field. If you type in a message and hit return it should display in the list.
Right now our app is looking a bit bland. To add a bit of flare, we'll add a header with the title of our app. Update the render
inside App.js to the following:
render() {
return (
<div className="App">
<div className="App-header">
<h1>My Chat App</h1>
</div>
<Messages
messages={this.state.messages}
currentMember={this.state.member}
/>
<Input
onSendMessage={this.onSendMessage}
/>
</div>
);
}
We just added a new div
with the title of our app. Head over to the browser. You should see a nice, grey header above your messages list.
Now it's looking like a real chat app! Congratulations! Now let's get our hands dirty and connect everything to Scaledrone.
Hooking It Up
Thankfully Scaledrone makes the business logic of making a chat app really simple. But before we start using it we need to make sure Scaledrone is included in our app.
Open public/index.html. Inside the HTML head
tag, add the following line to the top:
<script src='https://cdn.scaledrone.com/scaledrone.min.js'></script>
This will make sure Scaledrone's code is included in our app. We're almost ready to start using it, but we need to do one more thing first: create a Scaledrone channel.
To successfully connect to Scaledrone you need to get your own channel ID from the Scaledrone's dashboard. If you haven't already, head over to Scaledrone.com and register an account. Once you've done that, go to your dashboard and click the big green +Create Channel button to get started. You can choose Never require authentication for now. Note down the channel ID from the just created channel, you'll need it in a bit.
Head back to App.js and add a constructor to the class:
constructor() {
super();
this.drone = new window.Scaledrone("YOUR-CHANNEL-ID", {
data: this.state.member
});
this.drone.on('open', error => {
if (error) {
return console.error(error);
}
const member = {...this.state.member};
member.id = this.drone.clientId;
this.setState({member});
});
}
This will instantiate a new instance of Scaledrone. Make sure to replace the string with the channel ID you got earlier. Because we're loading the script inside our HTML head
tag, the script's contents will be attached to the global window
object, which is where we'll get a Scaledrone instance.
We'll also pass Scaledrone the data for the currently logged in member. If you have additional data about the user or the client this is a good way to supply that data, instead of having to send it with each message.
Next, we need to connect to a room. A room is a group of users that we can send messages to. You listen to those messages by subscribing to a room of a specific name. Add the following code to the end of the constructor:
const room = this.drone.subscribe("observable-room");
We'll subscribe to a room.
Note: You might have noticed that we named our name Scaledrone room
observable-room
. You can name the room anything you want, a single user can actually connect to an infinite amount of rooms for all sorts of application scenarios. However, in order for messages to contain the info of the sender, you need to prefix the room name with"observable-"
. Read more...
We also need to know when the messages arrive, so we'll subscribe to the "data" event on the room. Add the following code to the end of the constructor:
room.on('data', (data, member) => {
const messages = this.state.messages;
messages.push({member, text: data});
this.setState({messages});
});
When we get a new message, we'll add the message's text as well as the client data to the our state. Next, we need to modify onSendMessage
to publish a new message to all the other users in the room:
onSendMessage = (message) => {
this.drone.publish({
room: "observable-room",
message
});
}
Scaledrone makes this a simple matter of calling the publish
function with the data.
Finally, we'll remove the test message from our app's state
so that the initial state looks like the following:
state = {
messages: [],
member: {
username: randomName(),
color: randomColor()
}
}
If you go to your web browser now the behavior will look the same: by sending a new message it should appear in the list. The difference is that this time it's actually being sent to Scaledrone. You can open the app in another tap and chat with yourself. :)
And We're Done!
Congrats, you have built a React chat app! With Scaledrone, you get to focus on making your UI beautiful, and on the functionality of your app, without worrying about the back end. You can find the full source code on GitHub.
Here are some feature ideas to improve on this app using Scaledrone's APIs:
- Display who's online
- Show when a user is typing
- Send messages and attachments
- Authentication
You can find the full source code or run the working prototype on GitHub. If you have any questions or feedback feel free to contact us.