DEV Community

Cover image for How to use socket.io-client correctly in React app
bravemaster619
bravemaster619

Posted on • Updated on

How to use socket.io-client correctly in React app

Background

The following is my first blog to achieve 8k+ view and 1st in Google SERP rank. However, it has some concerns and I decided to rewrite it.

In this article, I used global socket variable to manage socket events in a React app. Like the following:

// service/socket.js
export const socket = socketio.connect(SOCKET_URL);
// elsewhere
import {socket} from "service/socket";
Enter fullscreen mode Exit fullscreen mode

However, fellow devs recommended to use React context API in their comments. And I began to think that using global variable is not a React-way either. (Although I believe that works too. Because socket does not change its state).

I updated my previous article, but I think it needs more explanation. So I decided to write an article that shows how to use React context API to manage one global socket instance.

1. Create Socket Context

We will use useContext hook to provide SocketContext to entire app.

Create a file in context/socket.js:

import socketio from "socket.io-client";
import { SOCKET_URL } from "config";

export const socket = socketio.connect(SOCKET_URL);
export const SocketContext = React.createContext();
Enter fullscreen mode Exit fullscreen mode

2. Use socket context and provide a value

Add SocketContext provider at the root of your project or at the largest scope where socket is used:

import {SocketContext, socket} from 'context/socket';
import Child from 'components/Child';

const App = () => {
  return (
    <SocketContext.Provider value={socket}>
      <Child />
      <Child />
      ...
    </SocketContext.Provider
  );
};
Enter fullscreen mode Exit fullscreen mode

3. Now you can use socket in any child component

For example, in GrandChild component, you can use socket like this:

import React, {useState, useContext, useCallback, useEffect} from 'react';
import {SocketContext} from 'context/socket';

const GrandChild = ({userId}) => {

  const socket = useContext(SocketContext);

  const [joined, setJoined] = useState(false);

  const handleInviteAccepted = useCallback(() => {
    setJoined(true);
  }, []);

  const handleJoinChat = useCallback(() => {
    socket.emit("SEND_JOIN_REQUEST");
  }, []);


  useEffect(() => {
    // as soon as the component is mounted, do the following tasks:

    // emit USER_ONLINE event
    socket.emit("USER_ONLINE", userId); 

    // subscribe to socket events
    socket.on("JOIN_REQUEST_ACCEPTED", handleInviteAccepted); 

    return () => {
      // before the component is destroyed
      // unbind all event handlers used in this component
      socket.off("JOIN_REQUEST_ACCEPTED", handleInviteAccepted);
    };
  }, [socket, userId, handleInviteAccepted]);

  return (
    <div>
      { joined ? (
        <p>Click the button to send a request to join chat!</p>
      ) : (
        <p>Congratulations! You are accepted to join chat!</p>
      ) }
      <button onClick={handleJoinChat}>
        Join Chat
      </button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

OK, here are some explanations:

What is useContext?

  • useContext provides a React way to use global state
  • You can use context in any child component
  • Context values are states. React notices their change and triggers re-render.

What is useCallback? Why did you put every handlers inside useCallback?

  • useCallback prevents reassigning whenever there is state update
  • Functions will be reassigned only when elements in the second argument are updated
  • Since we passed an empty array to second argument, functions are assigned for only once
  • You may forget (or don't bother) to use useCallback. But you may face serious performance issues if there are many states and components in your project

What is useEffect and that [socket] array provided as the second argument?

  • The second argument is called dependency array. React will watch dependency array elements and whenever one of them is updated, the first argument function will be executed.

  • If you omit dependency array in useEffect, the function will be executed whenever there is a state update.

  • If the dependency array is an empty array, the function will be executed only once.

  • In React functional component, you can write componentDidMount and componentWillUnmount alternatives in the following way:

useEffect(() => {
  // here is componentDidMount
  return () => {
    // here is componentWillUnmount
  }
}, []);
Enter fullscreen mode Exit fullscreen mode
  • It is strongly recommended to put every state that's used in the first argument function to the dependency array.

BONUS

If you want to use JWT token to authenticate socket connections, you can do like the following:

const getSocket = () => {
  const token = getAuthToken(); // get jwt token from local storage or cookie
  if (token) {
    return socketio.connect(SOCKET_URL, {
      query: { token }
    });
  }
  return socketio.connect(SOCKET_URL);
};
Enter fullscreen mode Exit fullscreen mode

Then in socket server, you can get jwt token like the following:

import SocketIO from "socket.io";

const io = new SocketIO.Server(expressApp);
const jwtMiddleware = (socket, next) => {
  const {token} = socket.handshake.query;
  // verify token
};

io.use(jwtMiddleware);
Enter fullscreen mode Exit fullscreen mode

Discussion (12)

Collapse
lethanhvietctt5 profile image
Le Thanh Viet

I have learned about ReactJS and Socket recently and written an video call and chat app using ReactJS and Socket .Here is my source code: github.com/lethanhvietctt5/video-c.... I hope it can help you guys to build a video chat app.

Collapse
arkadiuszkozub profile image
Arkadiusz Kozub

I don't think it's good approach. It will work for very small projects, but for larger ones not so much. Mixing socket communication with React will become overwhelming as app will grow, because all of it will spread through entire project. I would separate data source from frontend components. Imho better method is to extract actions and datasource to another files and provide only api to front components (as react-redux does). It would provide flexibility. Especially when you need to access different data sources (ie. local storage + cookie + websocket + ajax). React shouldn't "care" about where data comes from, business logic or communication with server.

Collapse
christopherhbutler profile image
Christopher Harold Butler

Great article! Could you show how to implement 'user is typing indicator' with react? It seems when you keep track of a keyPress using a setTimeout you get a call to a function that sets a variable for every keyPress. I can share a code example somewhere if you want to discuss :)

Collapse
bravemaster619 profile image
bravemaster619 Author

Thanks! If you want to ask me anything, please open a stackoverflow post and share the link. I'm more than happy to help there :)

Collapse
goran7777 profile image
Goran

Elswhere in react on that way doesnt work,we can have only one instance of socket on client.

Collapse
mirik999 profile image
mirik999

Good example. Thanks

Collapse
dtweblife profile image
Daniel Thomas

I don't understand the JWT part. It's not written in relation to context. I have my app with a callback that is receiving the jwt on login then storing in storage which is when I want to connect to the socket, but I'm not too sure how to pass that jwt to context or force it to update when the jwt is available.

Collapse
sajaddp profile image
Sajad Dehshiri

@bravemaster619
It is greate article, Thank you!

When the server emit it sends some values. How we can access these values in useCallback‍?

Collapse
derockypeter profile image
Amaizu Somto Peter

Nice tutuorial. In my VS CODE console, I keep getting multiple request with polling, but it never does a a handshake with the server, what could be wrong?

Collapse
lizardkinglk profile image
sndp

And for initialize and listen of the events of the socket, it is better to use different useEffect hooks separately.

Collapse
bunty9 profile image
Bipin C

how do you globalize the socket instanse? ,,, so that i can call emits and listen to them in other modules of the project.

Collapse
chrisnabayuaji profile image
Chrisna Bayu Aji

How to handle when client disconnected from server.