How To A Build Real-time React App With Server-Sent Events

Eniola Lucas Fakeye
Geek Culture
Published in
8 min readJun 2, 2021

--

Photo by Szabo Viktor on Unsplash

Introduction

The ability to update content in real-time can make the difference between a good and bad user experience. Imagine having to reload your browser to see if the person you are chatting with has sent a new message.

Some of the ways to achieve real-time content update on the web are long-polling, web sockets and server-side events.

With long-polling an HTTP request is made to the server at a predefined interval. In server-side events, the browser’s event source API is used to open a channel of communication between the client and the server for updates to flow from the server to the client. The web socket protocol opens a two-way communication channel between the client and the server to allow updates to move in both ways.

The purpose of this article is to teach you how to leverage the browser EventSource API to build a real-time React application, its limitation and use cases. On that basis, this article shall be divided into three parts.

1. The Anatomy Of Server-Sent Events

Server-sent events are updates sent in the form of DOM events from the server to the browser. The browser uses the EventSource interface to establish a connection capable of receiving these types of event from the server. An event handler can then registered to handle the incoming event. These can be achieved in a couple of lines of code, depending on is to be done with the received event. Let's say we want to log the event on the console, it can be done in two lines of code:

const source = new EventSource(URL)
source.onmessage = e => console.log(e.data)
  1. Create an EventSource instance and specify the event endpoint
  2. Register an event handler to handle incoming events

Yes, it can be that simple.

1.1 Pros

Setting up a web application to receive server-side events is really easy, as seen in the example above. There’s no need for learning how to use or installing a new library because the EventSource interface is a browser API.

Also, server-side events is supported by majority of browsers.

While the scope of the article revolves around the frontend, it is worth mentioning that setting up a server to send server-side events to the client is relatively easy compared to web sockets. This is because all you need to do is send an HTTP response with a Content-Type value of text/event-stream; thereby making it language-agnostic and third-party-library-free.

1.2 Cons and Comparison With WebSocket

With server-sent events only the server can send updates to the client, the client cannot send updates to the server through the same channel i.e the channel of communication is unidirectional.WebSockets, on the other hand, allows full-duplex communication between the server and the client; that is, updates can be sent in both direction in the same connection.

While the web socket protocol support binary data types, server-sent events only support texts.

Applications Of Sever-Sent Events.

Server-Side Events are a perfect fit for almost all scenarios in which the server the major or only player responsible for providing updates. Take a stock price app for an example, the price of the stock isn’t updated by user input but it’s calculated by an algorithm and the server can thus update the client when there is a change in price. Other examples of the applications of Server-Side Events include but not limited to:

  1. A real-time news or information application.
  2. An application to display the status of an ongoing process.

A Real-Time React Application Built With The EventSource API.

3.1 About The Application

We shall be building an application that displays the real-time stock prices of selected companies. The prices are not real they are random numbers generated on the server. We shall also create a simple express server from which the react app can get stock prices.

3.2 The React Application

The react application is quite simple, it’s a single-component [create react app boilerplate](https://create-react-app.dev/docs/getting-started/) that does not utilize any external library. Below is the code of the single-component i.e App.js

/*App.js*/import React, { useState, useEffect } from "react";
import "./App.css";
const BaseURL = "<http://localhost:8000>";function App() {
const [status, setStatus] = useState("idle");
const [stockPrices, setStockPrices] = useState([]);
const formatPrice = (price) => {
return new Intl.NumberFormat("us-EN", {
style: "currency",
currency: "USD",
currencyDisplay: "narrowSymbol",
}).format(price);
};
const fetchStockPrice = () => {
setStatus("idle");
fetch(`${BaseURL}/stocks`, { method: "GET" })
.then((res) => (res.status === 200 ? res.json() : setStatus("rejected")))
.then((result) => setStockPrices(result.data))
.catch((err) => setStatus("rejected"));
};
const updateStockPrices = (data) => {
const parsedData = JSON.parse(data);
setStockPrices((stockPrices) =>
[...stockPrices].map((stock) => {
if (stock.id === parsedData.id) {
return parsedData;
}
return stock;
})
);
};
useEffect(() => {
fetchStockPrice();
const eventSource = new EventSource(`${BaseURL}/realtime-price`);
eventSource.onmessage = (e) => updateStockPrices(e.data);
return () => {
eventSource.close();
};
}, []);
return (
<div className="App">
<table>
<caption>Stock Prices</caption>
<thead>
<tr>
<th>S/N</th>
<th>Ticker Symbol</th>
<th>Real Time Price</th>
</tr>
</thead>
<tbody>
{stockPrices.map(({ id, ticker, price }, index) => (
<tr key={id}>
<td>{index + 1}</td>
<td>{ticker}</td>
<td>{formatPrice(price)}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
export default App

Here’s what is going on in the code above:

  1. formatPrice(price) This function takes in an integer and utilizes the Intl browser API to format it to standard US dollars currency format. The narrowSymbol of the currencyDisplay property specifies the use of the dollar sign ($) instead of (USD). You can read more about the Intl API here.
  2. The fetchStockPrice() function makes an HTTP request to the server using the browser's Fetch API. This request gets the current price from the server when the component mounts.
  3. updateStockPrices(data) this function handles the events received from the server. The onmessage event handler will call this function and pass the data property of the event received from the server to it. This function will in turn update the app state using the setStockPrices() function.
  4. The useEffect() hook will fetch the current prices of stock when the components mount by calling the fetchStockPrice() function. After which it will set up an EventSource connection with the server together with a handler that will respond anytime the client receives an event from the server.
  5. The function component i.e App() returns an HTML table and the below is the content of the stylesheet that was used to apply basic styling to the table
/* App.css */table {
width: 80%;;
}
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
th, td {
padding: 5px;
text-align: left;
}

3.3 The NodeJS Server

The Node JS server is an instance of an Express JS server that is configured to run on port 8000. It has two GET routes, /stocks route that supplies the current price of stocks and the /realtime-price route that supplies the update stock price in real-time. Below is the complete content of the Node JS server:

/*server.js*/const express = require("express");
const cors = require("cors");
const app = express();
app.use(cors());
const PORT = 8000;
const stocks = [
{ id: 1, ticker: "AAPL", price: 497.48 },
{ id: 2, ticker: "MSFT", price: 213.02 },
{ id: 3, ticker: "AMZN", price: 3284.72 },
];
function getRandomStock() {
return Math.round(Math.random() * (2 - 0) + 0);
}
function getRandomPrice() {
return Math.random() * (5000 - 20) + 20;
}
app.get("/stocks", function (req, res) {
res.status(200).json({ success: true, data: stocks });
});
app.get("/realtime-price", function (req, res) {
res.writeHead(200, {
Connection: "keep-alive",
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
});
setInterval(() => {
res.write(
"data:" +
JSON.stringify({ ...stocks[getRandomStock()], price: getRandomPrice() })
);
res.write("\\n\\n");
}, 10000);
});
app.listen(PORT, function () {
console.log(`Server is running on ${PORT}`);
});

Here’s what is going on in the code above:

  1. Importation of needed libraries and instance of the server.
const express = require("express");
const cors = require("cors");
const app = express();

The express and cors libraries were imported into the app and an instance of an Express JS server (app)was created.

NB: You have to install express and cors libraries before you can import them. Run the command below to do so:

$ yarn add express cors

If you use NPM, replace yarn with npm.

  1. Setting up of port and cors configuration.
app.use(cors());
const PORT = 8000;
const stocks = [
{ id: 1, ticker: "AAPL", price: 497.48 },
{ id: 2, ticker: "MSFT", price: 213.02 },
{ id: 3, ticker: "AMZN", price: 3284.72 },
];
function getRandomStock() {
return Math.round(Math.random() * (2 - 0) + 0);
}
function getRandomPrice() {
return Math.random() * (5000 - 20) + 20;
}
  • The app is configured to listen to request from all origin using the cors library. This configuration is for testing purposes only, do not replicate in production. Also a constant, PORT was created to hold the value of the port that will eventually be passed to the listen method of the Express JS library.
  • A stocks array was created to hold information on companies and the prices of their respective stocks.
  • getRandomStock() is a function that returns a whole number between 0 and 2 which are the boundary indexes of the stocks array. Its purpose is to return a random stock index from the array of stocks.
  • getRandomPrice() returns a random decimal that is not greater than 5000. The purpose is to generate random prices that simulate the fluctuation in stock prices.
  1. The /stock route.
app.get("/stocks", function (req, res) {
res.status(200).json({ success: true, data: stocks });
});

A GET route was created to send out the current prices of stock.

  1. The /realtime-price route.
app.get("/realtime-price", function (req, res) {
res.writeHead(200, {
Connection: "keep-alive",
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
});
setInterval(() => {
res.write(
"data:" +
JSON.stringify({ ...stocks[getRandomStock()], price: getRandomPrice() })
);
res.write("\\n\\n");
}, 10000);
});
  1. Listening to request.
app.listen(PORT, function () {
console.log(`Server is running on ${PORT}`);
});

Finally, the server is configured to listen to request on the predetermined port (8000). You can start the react app by navigating to the root of your app and running yarn start on your terminal. You can also start the server by running node server.js on your terminal from the root of the server's directory. Change yarn to npm if your installations were done using npm.

3.4 Conclusion

The primary aim of this article is to teach the fundamentals of Server-Sent Event and how to use it in a React application. While the Event-Source interface is native to the browser and can be utilized without using a library or framework; React was used in this article because of its popularity. There’s more to explore on Server-Sent Events e.g event types, reconnection and more. Please feel free to explore once you are familiar with the basics. Cheers.

--

--

Eniola Lucas Fakeye
Geek Culture

Software Engineer | Startup Enthusiast | Passionate Learner