Server-Sent Events and React

sirwanafifi

Sirwan Afifi

Posted on December 23, 2019

Server-Sent Events and React

Originally posted on my blog

Server Sents Events are realtime events sent from the server to the client. It's a way to subscribe to a data stream sent by a server. Basically, it is a long-running HTTP connection with a particular mime type. Contrary to WebSocket, Server-Sent Events are unidirectional which clients subscribe to a channel and get data from the server. Updates for the data are pushed to the client in realtime as they occur, so the client doesn't need to initiate any requests. When using SSE, we should consider these:

  • Requests can be redirected HTTP 301(permanent) & 307(temporary)
  • Only UTF-8 decoding is supported, no binary data
  • Protocol supports multiple type of events, default is message
  • Clients always reconnect (no need to handle)
  • Server sends HTTP 204 No Content to stop reconnection
  • Limited amount of global connections per site

Server Implementation:

Implementing it on the server is pretty easy. All we need to do is running an endless loop. Inside the loop, we should set the somespecial HTTP headers and push the data to the response every 2 seconds:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env,
        ILogger<Startup> loggerDebug, Datasource datasource)
{
    app.UseCors("MyPolicy");

    app.UseRouting();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/stream", async context =>
        {
            var response = context.Response;
            response.Headers.Add("connection", "keep-alive");
            response.Headers.Add("cach-control", "no-cache");
            response.Headers.Add("content-type", "text/event-stream");

            while (true)
            {
                await response.Body
                    .WriteAsync(Encoding.UTF8.GetBytes($"data: {JsonSerializer.Serialize(datasource.GetData())}\n\n"));

                await response.Body.FlushAsync();
                await Task.Delay(2 * 1000);
            }

        });
    });
}
Enter fullscreen mode Exit fullscreen mode

Here's also a Node.js version of the server:

app.get("/stream", (req, res) => {
  res.set({
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    Connection: "keep-alive",

    // enabling CORS
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Headers":
      "Origin, X-Requested-With, Content-Type, Accept",
  })

  setInterval(() => {
    res.write(`data: ${JSON.stringify(getData())}\n\n`)
  }, 2000)
})
Enter fullscreen mode Exit fullscreen mode

As you can see, we are sending the data in a specific format:

data: My message\n\n
Enter fullscreen mode Exit fullscreen mode

Frontend Implementation

Implementing it on the client is fairly easy. All we need to do is making use of EventSource API, which is a standard interface to interact with the Server-Sent Events protocol. It basically opens a persistent connection to our HTTP server. The cool thing about the API is that it keeps the connection open until we call EventSource.close():

import React from "react"

const ProductList = () => {
  const [data, setData] = React.useState([] as any)

  const formatter = new Intl.NumberFormat("en-GB", {
    style: "currency",
    currency: "gbp",
  })

  React.useEffect(() => {
    let eventSource = new EventSource("http://localhost:5000/stream")
    eventSource.onmessage = e => updateProdutList(JSON.parse(e.data))
  }, [])

  const updateProdutList = (product: any) => {
    setData([...product])
  }

  return (
    <table className="table table-hover">
      <thead className="thead-dark">
        <tr>
          <th>Id</th>
          <th>Title</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>
        {data.map((p: any) => (
          <tr key={p.Id}>
            <td>{p.Id}</td>
            <td>{p.Title}</td>
            <td>{formatter.format(p.Price)}</td>
          </tr>
        ))}
      </tbody>
    </table>
  )
}

export { ProductList }
Enter fullscreen mode Exit fullscreen mode

Here we have passed in the SSE endpoint URL to the EventSource constructor. It then goes and establishes the communication channel between the React app and the server. Then we added onmessage event handler which is called when new data is received. updateProdutList is responsible for updating the state so once the data is received we update the state with the latest data. The process of subscribing to the endpoint happens once the ProductList component is mounted; that's why we used useEffect inside the component.

You can see the output here
Source code (https://github.com/SirwanAfifi/realtime)

💖 💪 🙅 🚩
sirwanafifi
Sirwan Afifi

Posted on December 23, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Server-Sent Events and React
aspnetcore Server-Sent Events and React

December 23, 2019