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
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?
The cleanup function in a useEffect will run in two cases:
(which it won’t run a second time as your dependency array
is empty)
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:
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.