DEV Community

Cover image for Using WebSockets with React
Murat Can Yüksel
Murat Can Yüksel

Posted on

Using WebSockets with React

For my last project, I had to use Websockets to create a website that displays real-time trading data. I didn't know anything about WebSockets, and it took a couple of dreading hours to get to start with it. That's the only problem, actually, to start with it; the rest is pretty clear. This short article hopes to help others save the time it took for me to understand the basics of it.

Most of the tutorials on the web mention a "require" syntax. You know it. When you want to use a certain tool, component, or image in your component in JS or React, you'd do something like const something = require ("./folder/something"). Now, as I said, most of the tutorials I've found on the web start with this very syntax, that pushes you to start working with WebSockets using the require syntax. This is unnecessary, and maybe even wrong in the present day. I'm not sure about whether it works in any way or not, but I'm certain that the way I use works perfectly as I write this article on 12/09/2021. So, without further ado, let's talk about how we can make use of this protocol.

This article supposes that you have a working knowledge of Vanilla JS and React.js, you know how to deal with json format, and asynchronous code.

I initiate my app with vite (with the following command: npm init vite@latest and choose react from the menu), but you can use your own structure, or create-react-app. It doesn't matter really.

For a more in-depth introduction on WebSocket, visit javascript.info

What we'll build?

We're going to build a very simple, one-page React.js application that takes continuous-data from bitstamp.net and displays it on the page. The data will be changing all the time.

It doesn't really matter which service you're using, as long as it's WebSockets, the rest is plain Javascript.

Building the app

Let's start with connecting to bitstamp's WebSocket protocol by writing the following code const ws = new WebSocket("wss://ws.bitstamp.net"); Now, using this ws constant, we can subscribe to any channel that's defined on bitstamp's website and get continuous-data from there. You can find any information regarding the channels, properties, and all from here

Now, let's subscribe to a channel. I'll subscribe to oder_book_v2 channel and specify that I want to see btc/usd exchange rates. This call is defined in bitstamp's guide. You can change the channel and the currencies as you wish. Here's is the call:

 const apiCall = {
    event: "bts:subscribe",
    data: { channel: "order_book_btcusd" },
  };
Enter fullscreen mode Exit fullscreen mode

The next step is to send this call to the server on open =>

  ws.onopen = (event) => {
    ws.send(JSON.stringify(apiCall));
  };
Enter fullscreen mode Exit fullscreen mode

Now we want to do something with each data. So, whenever we receive a message from the server, we'll do something. Let's write an async code with try/catch


ws.onmessage = function (event) {
const json = JSON.parse(event.data);
console.log(`[message] Data received from server: ${json}`);
try {
if ((json.event = "data")) {

        console.log(json.data);
      }
    } catch (err) {
      // whatever you wish to do with the err
    }

};
Enter fullscreen mode Exit fullscreen mode

If we opened the console, we'd see a large amount of data coming from the server. That's it, actually. We got the data, it's coming in a stream, and we can do whatever we want to do with it. It's that easy.

I want to display the data in a particular manner though. Let me paste the code and I'll explain immediately after:

import React, { useState } from "react";

function  App() {
  //give an initial state so that the data won't be undefined at start
  const [bids, setBids] = useState([0]);

  const ws = new WebSocket("wss://ws.bitstamp.net");

  const apiCall = {
    event: "bts:subscribe",
    data: { channel: "order_book_btcusd" },
  };

  ws.onopen = (event) => {
    ws.send(JSON.stringify(apiCall));
  };

  ws.onmessage = function (event) {
    const json = JSON.parse(event.data);
    try {
      if ((json.event = "data")) {
        setBids(json.data.bids.slice(0, 5));
      }
    } catch (err) {
      console.log(err);
    }
  };
  //map the first 5 bids
  const firstBids = bids.map((item) => {
    return (
      <div>
        <p> {item}</p>
      </div>
    );
  });

  return <div>{firstBids}</div>;
}

export default  App;
Enter fullscreen mode Exit fullscreen mode

So, what's going on here? As you can see, it's a very basic React.js App component. I use useState hook so I import it also with react.

I define the state and give it an initial value.

I proceed as indicated before- except, I set the state to json.data.bids (bids being a property of the live order channel and indicated on bitstamp's page) and restrict the amount of data I'll receive to 5, for the sake of convenience.

I map the data I receive, saved in state (as you know, React asks for a key for each item. I won't be using it here. I usually use uniqid for that, you can check it out yourself.)

I return the mapped data and voilà! If you did the same, you should see exactly 5 rows of constantly changing data on the screen.

I hope this article helps someone.

All the best and keep coding!

Discussion (15)

Collapse
vu profile image
Vu

I guess your code will create multiple instances of Websocket, you should put this in useEffect hook to avoid that

Collapse
muratcanyuksel profile image
Murat Can Yüksel Author

Hello, I have 4 components that I repeat the same method and it seems like each one waits its turn to display the data. I was wondering how to fix that, I guess you're taling about that? In my original code, I have useEffect in this manner :

 useEffect(() => {
    ws.onmessage = function (event) {
      const json = JSON.parse(event.data);
      try {
        if ((json.event = "data")) {
          dispatch(getOrderBookData(json.data));
        }
      } catch (err) {
        console.log(err);
      }
    };
    //clean up function
    return () => ws.close();
  }, []);
Enter fullscreen mode Exit fullscreen mode

What should I do to overcome the problem you mentioned?

Collapse
vsalvans profile image
Víctor Salvans Montesó

That's right. I will even go further and create a hook ( useWebsocket that uses useEffect internally) where you can pass the callback functions onopen and onmessage and returns a send function for instance or whatever you need from the hook.

Collapse
muratcanyuksel profile image
Murat Can Yüksel Author

Hey man, I wrote a reply to Vu's comment. Feel free to join the discussion :)

Thread Thread
vu profile image
Vu • Edited

Every time the client receives new message => setBids called => component rerender => create new instance of Websocket => create new connection (1 request in devtools). This's not good, right :)))
This is my idea. You can check more in network tab in devtools and see the differents.


const apiCall = {
    event: 'bts:subscribe',
    data: { channel: 'order_book_btcusd' },
};
function App() {
    const [bids, setBids] = useState([0]);

    useEffect(() => {
        const ws = new WebSocket('wss://ws.bitstamp.net');
        ws.onopen = (event) => {
            ws.send(JSON.stringify(apiCall));
        };
        ws.onmessage = function (event) {
            const json = JSON.parse(event.data);
            try {
                if ((json.event == 'data')) {
                    setBids(json.data.bids.slice(0, 5));
                }
            } catch (err) {
                console.log(err);
            }
        };
        //clean up function
        return () => ws.close();
    }, []);
    const firstBids = bids.map((item, index) => (
        <div key={index}>
            <p> {item}</p>
        </div>
    ));

    return <div>{firstBids}</div>;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
mzrt profile image
Evgeniy Tortumashev

if ((json.event = "data")) {

Why "=", and not "==="?

Collapse
dcsan profile image
dc

Because he's not using a linter?
That's why he gets a lot of unfiltered events.

Collapse
muratcanyuksel profile image
Murat Can Yüksel Author

Good question, that was some snipped I found while trying to figure out how to get started, and itseemed to work. I didn't even think about it. Will it cause any problems? It didn't so far...

Collapse
mzrt profile image
Evgeniy Tortumashev

This condition will always be true because the assignment operator is used instead of the comparison operator.

Thread Thread
muratcanyuksel profile image
Murat Can Yüksel Author

Gonna edit it (y)

Collapse
codecustard profile image
Emmanuel Barroga

Why use websocket and not socket.io? Just asking to exchange some dialog between the two.

Collapse
muratcanyuksel profile image
Murat Can Yüksel Author

To be honest, I'm not even sure what's the other one. I had 3 days to learn websockets and redux, so I went with that way.

What's the difference between the two? Why should I check socket.io?

Collapse
vsalvans profile image
Víctor Salvans Montesó • Edited

Websocket is the native api from your browser whereas socket.io does not implent the same api although it can use websocket under the hood if the browser support it, besides it has the server version that helps the backend keep the same interface with the client and add extra features as authentication , etc.

Collapse
mattcale profile image
Matthew Cale

Clear, concise and useful! Thanks for sharing. As others have mentioned using the useEffect hook is likely the best spot for initializing WS, but it adds additional code so 🤷‍♀️. In any case really great work

Collapse
antonioallen77 profile image
AntonioAllen77

Once the WebSocket server is created, we had to accept the handshake on receiving the request . dua to make someone call you