A Simple Chat App With React, Node and WebSocket

Dan Kaufhold
Bitlab Studio
Published in
4 min readSep 16, 2018

--

In this quick guide I will be showing you the minimal code required to get a chat room up and running using React, Node and WebSocket.

It is a rudimentary summary of what I learned during a project for Bitlab Studio.

From there you can play around and explore and add own ideas and features.

In the end you will have an app that looks something like this:

You see a React app running in two browser windows that post their messages to a node app in the background, which broadcasts all messages back to all connected apps.

So let’s see how this is done!

Step 1: Prepare The Backend

Create a simple WebSocket server that broadcasts all incoming messages to everyone that’s connected. So in your project’s root directory run the following commands to create a separate backend directory and install ws :

mkdir backend
cd backend
yarn add ws

Then we will also need the actualserver.js file. Which is merely the following:

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 3030 });

wss.on('connection', function connection(ws) {
ws.on('message', function incoming(data) {
wss.clients.forEach(function each(client) {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
});
});

On each incoming message it sends it back to all connected clients.

Step 2: Add The Frontend

Optional - if you haven’t already: Install CRAyarn global add create-react-app

Then back in your project root you want to create a new react app:

create-react-app frontend
cd frontend
yarn add prop-types

You now have a frontend/ and a backend/ directory.

Create ChatMessage.js component in the new frontend/ directory. This determines how each single chat message will look like:

import React from 'react'

export default ({ name, message }) =>
<p>
<strong>{name}</strong> <em>{message}</em>
</p>

It will output the name of the user in bold and then the message.

Create a ChatInput.js component. We will display it on top of the chat to enter new messages:

import React, { Component } from 'react'
import PropTypes from 'prop-types'

class ChatInput extends Component {
static propTypes = {
onSubmitMessage: PropTypes.func.isRequired,
}
state = {
message: '',
}

render() {
return (
<form
action="."
onSubmit={e => {
e.preventDefault()
this.props.onSubmitMessage(this.state.message)
this.setState({ message: '' })
}}
>
<input
type="text"
placeholder={'Enter message...'}
value={this.state.message}
onChange={e => this.setState({ message: e.target.value })}
/>
<input type="submit" value={'Send'} />
</form>
)
}
}

export default ChatInput

This component receives one prop onSubmitMessage from the Chat component we’re about to create, which is called when the form is submitted. Additionally when the form is submitted, the message input is cleared again.

Now create the Chat.js component, which will be the center of our chat logic. It holds our state, manage the connection and also send and receive the messages:

import React, { Component } from 'react'
import ChatInput from './ChatInput'
import ChatMessage from './ChatMessage'

const URL = 'ws://localhost:3030'

class Chat extends Component {
state = {
name: 'Bob',
messages: [],
}

ws = new WebSocket(URL)

componentDidMount() {
this.ws.onopen = () => {
// on connecting, do nothing but log it to the console
console.log('connected')
}

this.ws.onmessage = evt => {
// on receiving a message, add it to the list of messages
const message = JSON.parse(evt.data)
this.addMessage(message)
}

this.ws.onclose = () => {
console.log('disconnected')
// automatically try to reconnect on connection loss
this.setState({
ws: new WebSocket(URL),
})
}
}

addMessage = message =>
this.setState(state => ({ messages: [message, ...state.messages] }))

submitMessage = messageString => {
// on submitting the ChatInput form, send the message, add it to the list and reset the input
const message = { name: this.state.name, message: messageString }
this.ws.send(JSON.stringify(message))
this.addMessage(message)
}

render() {
return (
<div>
<label htmlFor="name">
Name:&nbsp;
<input
type="text"
id={'name'}
placeholder={'Enter your name...'}
value={this.state.name}
onChange={e => this.setState({ name: e.target.value })}
/>
</label>
<ChatInput
ws={this.ws}
onSubmitMessage={messageString => this.submitMessage(messageString)}
/>
{this.state.messages.map((message, index) =>
<ChatMessage
key={index}
message={message.message}
name={message.name}
/>,
)}
</div>
)
}
}

export default Chat

Take a closer look at the Chat component if you haven’t already. It creates a new WebSocket instance and adds handlers for messages as well as opening and closing connections. It passes the onSubmitMessage prop to our ChatInput which actually sends the message to the node backend. In addition it displays a name input, which we also send to the backend to identify the users.

Update your App.js to include the Chat component, so it actually renders on screen.

import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'
import Chat from './Chat'

class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<Chat />
</div>
)
}
}

export default App

Step 3: Try It Out!

Start your servers by running yarn start in the frontend/ directory and node server.js in the backend/ directory and point your browser (or multiple browser windows) to localhost:3000

Congrats, you’ve created your first chat room 🎉

Step 4: Clone And Extend

If you want to dive into the full code example you can clone it here and play around with it: https://github.com/bitlabstudio/blogpost-react-websocket-chat

Want to know more about Bitlab Studio? Visit bitlabstudio.com!

Send me examples of what you’ve built on top of my code! I might feature links to them here, if you want :)

--

--