I'm building a chat component that subscribes to a WebSocket when mounted and should unsubscribe on unmount. The cleanup function in useEffect doesn't seem to fire when the component
is removed via a conditional render in the parent.
What I'm trying to do
Unsubscribe from the WebSocket connection when <ChatRoom /> is unmounted.
function ChatRoom({ roomId }) {
useEffect(() => {
const socket = connectToRoom(roomId);
console.log('subscribed:', roomId);
return () => {
console.log('unsubscribed:', roomId);
socket.disconnect();
};
}, [roomId]);
return <div>Room: {roomId}</div>;
}
function App() {
const [show, setShow] = useState(true);
return (
<>
<button onClick={() => setShow(s => !s)}>toggle</button>
{show && <ChatRoom roomId="general" />}
</>
);
}
What I expected
When I click the toggle button, I expect to see unsubscribed: general in the console.
What actually happens
I only see subscribed: general once on mount. The cleanup log never appears, and the socket stays open (verified in Network tab).
What I've tried
- Confirmed show is actually flipping to false (added a log in App)
- Removed
<StrictMode>to rule out double-invocation behavior - Checked that connectToRoom returns an object with
disconnect()
Environment
- React 18.2.0
- Vite 5.0
- Chrome 122