skip to Main Content

I’m building a test app that connects to a server via WebSocket. The app crashes when I don’t use the WebSocket.close() function in the cleanup part of the useEffect hook. The app works fine when WebSocket.close() is included, ofc I know I should close connection/clean after using it but error references the some kind of ?object? (Error is listed below). Why does this happen?

I’ve tried to understand the issue, and I wonder if it’s related to something that happens in the background. From what I know, cleanup happens after the render, so it shouldn’t directly impact the rendering or state before the component renders. Is my understanding correct, or am I missing something?

Uncaught runtime errors: ERROR An attempt was made to use an object that is not, or is no longer, usable ./src/CommunicationComponent.jsx/WebSocketComponent/
</socketRef.current.onopen@http://localhost:3000/static/js/bundle.js:191:27 EventHandlerNonNull*./src/CommunicationComponent.jsx/WebSocketComponent/<@http://localhost:3000/static/js/bundle.js:189:7 commitHookEffectListMount@http://localhost:3000/static/js/bundle.js:26115:30
  commitPassiveMountOnFiber@http://localhost:3000/static/js/bundle.js:27608:42 commitPassiveMountEffects_complete@http://localhost:3000/static/js/bundle.js:27580:38 commitPassiveMountEffects_begin@http://localhost:3000/static/js/bundle.js:27570:45 commitPassiveMountEffects@http://localhost:3000/static/js/bundle.js:27560:38
  flushPassiveEffectsImpl@http://localhost:3000/static/js/bundle.js:29443:32 flushPassiveEffects@http://localhost:3000/static/js/bundle.js:29396:18 ./node_modules/react-dom/cjs/react-dom.development.js/commitRootImpl/<@http://localhost:3000/static/js/bundle.js:29211:13
  workLoop@http://localhost:3000/static/js/bundle.js:36363:46 flushWork@http://localhost:3000/static/js/bundle.js:36341:18 performWorkUntilDeadline@http://localhost:3000/static/js/bundle.js:36578:25 EventHandlerNonNull*./node_modules/scheduler/cjs/scheduler.development.js/<@http://localhost:3000/static/js/bundle.js:36614:7
  ./node_modules/scheduler/cjs/scheduler.development.js@http://localhost:3000/static/js/bundle.js:36664:5 options.factory@http://localhost:3000/static/js/bundle.js:41901:31 __webpack_require__@http://localhost:3000/static/js/bundle.js:41331:32 fn@http://localhost:3000/static/js/bundle.js:41560:21
  ./node_modules/scheduler/index.js@http://localhost:3000/static/js/bundle.js:36679:20 options.factory@http://localhost:3000/static/js/bundle.js:41901:31 __webpack_require__@http://localhost:3000/static/js/bundle.js:41331:32 fn@http://localhost:3000/static/js/bundle.js:41560:21
  ./node_modules/react-dom/cjs/react-dom.development.js/<@http://localhost:3000/static/js/bundle.js:6083:40 ./node_modules/react-dom/cjs/react-dom.development.js@http://localhost:3000/static/js/bundle.js:31842:5 options.factory@http://localhost:3000/static/js/bundle.js:41901:31
  __webpack_require__@http://localhost:3000/static/js/bundle.js:41331:32 fn@http://localhost:3000/static/js/bundle.js:41560:21 ./node_modules/react-dom/index.js@http://localhost:3000/static/js/bundle.js:31913:20 options.factory@http://localhost:3000/static/js/bundle.js:41901:31
  __webpack_require__@http://localhost:3000/static/js/bundle.js:41331:32 fn@http://localhost:3000/static/js/bundle.js:41560:21 ./node_modules/react-dom/client.js@http://localhost:3000/static/js/bundle.js:31856:28 options.factory@http://localhost:3000/static/js/bundle.js:41901:31
  __webpack_require__@http://localhost:3000/static/js/bundle.js:41331:32 fn@http://localhost:3000/static/js/bundle.js:41560:21 ./src/index.js@http://localhost:3000/static/js/bundle.js:298:93 options.factory@http://localhost:3000/static/js/bundle.js:41901:31
  __webpack_require__@http://localhost:3000/static/js/bundle.js:41331:32 @http://localhost:3000/static/js/bundle.js:42544:56 @http://localhost:3000/static/js/bundle.js:42546:12

Working Client Code:

import React, { useEffect, useRef } from 'react';

export default function WebSocketComponent({ children }) {
  const socketRef = useRef(null);
  const isEstablish = useRef(false);  // Tracks if the WebSocket connection is established
  const inputRef = useRef(null);  // Ref for the input field

  useEffect(() => {
    if(!isEstablish.current)
    {
        socketRef.current = new WebSocket('ws://localhost:8080');

        socketRef.current.onopen = () => {
        console.log('WebSocket connection established.');
        socketRef.current.send('Hello Server!');
        isEstablish.current = true;  // Mark connection as established
        };

        socketRef.current.onmessage = (event) => {
        console.log('Message from server:', event.data);
        };

        socketRef.current.onerror = (error) => {
        console.error('WebSocket error:', error);
        };

        socketRef.current.onclose = () => {
        console.log('WebSocket connection closed.');
        isEstablish.current = false;  // Mark connection as closed
        };
    }
    // Cleanup function 
    return () => {
      if (socketRef.current) {
        socketRef.current.close(); //it must be here, return a error in other case
        isEstablish.current = false;
      }
    };
  }, []); 

  // Function to send data to the server
  const sendToServer = () => {

    if (isEstablish.current && socketRef.current.readyState === WebSocket.OPEN) {
      const message = inputRef.current.value;
      console.log('Sent to server:', message);
      socketRef.current.send(message);  // Send message only if connection is open
    } else {
      console.error('WebSocket is not open. Cannot send message.');
    }
  };

  return (
    <div>
      <h1>WebSocket Example</h1>
      <input ref={inputRef} />
      <button onClick={sendToServer}>Send</button>
    </div>
  );
}

2

Answers


  1. A quick guess: could this be related to the React.StrictMode?
    Is it enabled in the app? are you using React 18? does the same occur both in dev and prod mode?

    Login or Signup to reply.
  2. The cleanup function in a useEffect will run in two cases:

    1. When the useEffect is ran again
      (which it won’t run a second time as your dependency array
      is empty)
    2. When the component is unmounted.

    I think its safe to assume the component is unmounting when your app crashes, because the cleanup code should be running in case 2 above.

    If you see the socket is closing on its own, try using this as your onClose to see why:

    socketRef.current.onclose = (event) => {
      isEstablish.current = false;  // Mark connection as closed
      console.log(event.reason); // check reason socket is closing itself
    };
    

    From what I know, cleanup happens after the render, so it shouldn’t directly impact the rendering or state before the component renders. Is my understanding correct, or am I missing something?

    To answer this, yes cleanup is after the initial render at some point, but more specifically reasons 1 and 2 above.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search