Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WebSockets with functional components

I'm using websockets in my React application. The application is supposed to consist of function components only.

Imagine the application consists of two "tabs": one irrelevant, and the other one, a chat using websockets. Given that, we want to whip up a websocket connection once the user enters the chat tab.

How should the component handle the websocket object? Since we want to clean up once the user switches back to the other tab (WebSocket.close()), it sounds like we should be using an effect hook here.

const Chat = () => {
    const [messages, setMessages] = useState([]);
    useEffect(() => {
        const webSocket = new WebSocket("ws://url");
        webSocket.onmessage = (message) => {
            setMessages(prev => [...prev, message.data]);
        };
        return () => webSocket.close();
    }, []);
    return <p>{messages.join(" ")}</p>;
};

Works! But now, imagine we want to reference the webSocket variable somewhere outside the useEffect scope - say, we want to send something to the server once the user clicks a button.

Now, how should it be implemented? My current idea (albeit a flawed one, I feel) is:

const Chat = () => {
    const [messages, setMessages] = useState([]);
    const [webSocket] = useState(new WebSocket("ws://url"));
    useEffect(() => {
        webSocket.onmessage = (message) => {
            setMessages(prev => [...prev, message.data]);
        };
        return () => webSocket.close();
    }, []);
    return <p>{messages.join(" ")}</p>;
};

Kinda works, nevertheless I feel like there's a better solution.

like image 950
John Smith Avatar asked Oct 17 '19 11:10

John Smith


People also ask

Why you should not use WebSocket?

Avoid using WebSockets if only a small number of messages will be sent or if the messaging is very infrequent. Unless the client must quickly receive or act upon updates, maintaining the open connection may be an unnecessary waste of resources.

Does react support WebSockets?

There are multiple ways of adding WebSocket support to a React app. Each method has its pros and cons. This guide will go through some of the common patterns but will only explore in detail the pattern we are implementing.

How do WebSockets work internally?

WebSocket uses a unified TCP connection and needs one party to terminate the connection. Until it happens, the connection remains active. HTTP needs to build a distinct connection for separate requests. Once the request is completed, the connection breaks automatically.

Are WebSockets still used?

Websockets are largely obsolete because nowadays, if you create a HTTP/2 fetch request, any existing keepalive connection to that server is used, so the overhead that pre-HTTP/2 XHR connections needed is lost and with it the advantage of Websockets.


2 Answers

As @skyboyer has written in a comment under your question, you could use useRef hook to hold WebSocket and it will be more correct since you are not going to change or recreate WebSocket object. So you don't need useState hook.

The useRef() Hook isn’t just for DOM refs. The “ref” object is a generic container whose current property is mutable and can hold any value, similar to an instance property on a class. more

So you could change your code to:

const Chat = () => {
    const [messages, setMessages] = useState([]);
    const webSocket = useRef(null);

    useEffect(() => {
        webSocket.current = new WebSocket("ws://url");
        webSocket.current.onmessage = (message) => {
            setMessages(prev => [...prev, message.data]);
        };
        return () => webSocket.current.close();
    }, []);
    return <p>{messages.join(" ")}</p>;
};
like image 77
Andrii Golubenko Avatar answered Nov 15 '22 12:11

Andrii Golubenko


probably a good option is to have in a separate WS instance for manage the WS operations and used it where you need it, for example.

class WebSocketClient {
    static instance = null;
    callbacks = {};

    static getInstance() {
        if (!WebSocketClient.instance) WebSocketClient.instance = new WebSocketClient();
        return WebSocketClient.instance;
    }

    constructor() {
        this.socketRef = null;
    }

    addCallbacks = (...callbacks) => this.callbacks = { ...callbacks };

    connect = () => {
        const path = 'YOUR_SOCKET_PATH';
        this.socketRef = new WebSocket(path);
        this.socketRef.onopen = () => {
            console.log('WebSocket open');
        };

        this.socketRef.onmessage = e => {
            this.socketNewMessage(e.data);
        };

        this.socketRef.onerror = e => {
            console.log(e.message);
        };

        this.socketRef.onclose = () => {
            console.log("WebSocket closed let's reopen");
            this.connect();
        };
    }

    state = () => this.socketRef.readyState;

    waitForSocketConnection = (callback) => {
        const socket = this.socketRef;
        const recursion = this.waitForSocketConnection;
        setTimeout(
            () => {
                if (socket.readyState === 1) {
                    console.log("Connection is made")
                    if (callback != null) {
                        callback();
                    }
                    return;
                } else {
                    console.log("wait for connection...")
                    recursion(callback);
                }
            },
        1);
    }

}

export default WebSocketClient.getInstance();

So in your component will be.

import React, { useEffect } from 'react';
import WSC from 'wsc';


const Test = ({}) => {

    useEffect(()=>{
        WSC.connect();
        WSC.waitForSocketConnection(()=>{
            'HERE_YOUR_CALLBACKS'
        });
    },[])
}

With this way always return the same instance.

like image 26
Maximiliano Escobar Avatar answered Nov 15 '22 12:11

Maximiliano Escobar