A web app updates its state by requesting data from a server using HTTP requests. We do this using the Fetch API of a browser. But only the client can request an update of data from the server. The server itself can’t push updates to a client by itself.
One way to solve this is by using the browsers WebSocket API. It provides a two-way communication channel between a server and a client. This allows the server to send messages to the client, and vice versa.
What is a WebSocket?
An HTTP request is a one-time connection between server and client. It involves a request from the browser and a response from the server. A WebSocket is different. It’s a persistent connection between server and client, which allows communication in both directions. This is often called a full-duplex connection, like a phone line which allows both parties to talk at the same time.
Setting up a WebSocket Server
I used ws
to create a tiny NodeJS
WebSocket server in just a few lines of code. To do that install ws
using
yarn add ws
and create the file server.js
. In it we create a
WebSocketServer
serving on port 8080
. I want the server to regularly send
out a temperature reading to the client, which can then display it to the user.
When a WebSocket connection is made, the server sends a message to the connected
client every second. In this example we can simply generate some random
temperature data and timestamp it.
const { WebSocketServer } = require('ws')
const server = new WebSocketServer({ port: 8080 })
const getTemperatureReading = () => {
return Math.random() * 5 + 18 // generate a random temperature
}
server.on('connection', (ws) => {
setInterval(() => {
ws.send(
JSON.stringify({
temperature: getTemperatureReading()
timestamp: new Date(), // current timestamp
})
)
}, 1000) // 1000 milisecond interval
})
That’s all we need to do. Start the server by running node server.js
.
How to use a WebSocket in React
Now that the server is ready, we can consume the WebSocket data using a React app. I quickly set up a React app using Vite in which to use the following component.
Create a ServerTemperature
component with temperatureData
state variable. If
the data is undefined
, we render that no temperature data is available. If we
do have data, we render the temperature data.
import React, { useEffect, useState } from "react";
interface TemperatureData {
amount: number;
timestamp: Date;
}
const ServerTemperature: React.FC = () => {
const [temperatureData, setTemperatureData] = useState<TemperatureData>();
if (temperatureData === undefined) {
return <p>No temperature data.</p>;
}
return <p>{temperatureData.temperature.toFixed(1)} °C</p>;
};
Now its time to actually establish a WebSocket connection! To do so lets use
JavaScripts inbuilt WebSockets API. It’s important to only do this once, and not
on every re-render. A useEffect
Hook is perfect for that. Specifying an empty
dependency array []
makes sure to only establish the connection when the component
mounts.
useEffect(() => {
const webSocket = new WebSocket("ws://localhost:8000/temperature");
}, []);
We now have a WebSocket connection - but we still need to define what should
happen when we receive a message from the server. For that we add an onmessage
callback function to the instantiated WebSocket. It parses the message JSON
data and updates our state.
webSocket.onmessage = (event: MessageEvent) => {
const data = JSON.parse(event.data);
setTemperatureData({
amount: data.amount,
timestamp: new Date(data.timestamp),
});
};
Now run the React app and you will see that the component will update whenever it receives a new message from the server - every second.
You can find the entirety of the code on my GitHub.