I'm using Socket.IO
with React.js
and I want the websocket to start only when a specific component is rendered, so I use the websocket as a state of that component, like this:
const Comp = () => {
const [ws] = useState(socketIO());
// ... the rest of the component ...
};
But when the Comp
re-rendering, it creates new websocket connection, even I don't make any changes on the connection. After a while, I end up with more than 10 websocket connections. How can I make the component keep 1 connection only? I don't want the websocket connection to become global.
Remember that functional components are just functions, all the usual things you know about functions apply. Although hooks do some seeming-magic behind the scenes (it's not really magic, it's just they have context information we don't see), the code in your component function runs according to the usual rules. That means this code
const Comp = () => {
const [ws] = useState(socketIO());
// ... the rest of the component ...
};
will always call socketIO
so it can pass the return value of it into useState
.
To calculate a value only once, when the component is first created, use a ref (useRef
) to represent non-state instance information (the kind of thing you would have stored directly on a class component instance), like this:
const Comp = () => {
const {current: instance} = useRef({});
const ws = instance.ws = instance.ws || socketIO();
// ... the rest of the component ...
};
The {}
is still evaluated every time Comp
is called, but that overhead is trivial. What you get back from useRef
is an object with a current
property referring to a mutable object — one which will always be the same throughout the lifetime of the component. The second line uses the ws
property of that object, initializing it the first time if it's falsy.
This usage is called out in the useRef
docs:
However,
useRef()
is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.
Here's an example with a stand-in for socketIO
:
const { useRef, useState } = React;
function socketIO() {
console.log("socketIO called");
return {};
}
const Comp = () => {
console.log("Comp called");
const {current: instance} = useRef({});
const ws = instance.ws = instance.ws || socketIO();
const [counter, setCounter] = useState(0);
return (
<div>
{counter} <input type="button" value="+" onClick={() => setCounter(c => c + 1)} />
</div>
);
};
ReactDOM.render(<Comp/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
I should note that it's tempting to use useMemo
here, but useMemo
doesn't guarantee that your function to build the memoized result won't be called a second time. useMemo
is for performance optimization, not semantics. From the docs:
You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without
useMemo
— and then add it to optimize performance.
(their emphasis)
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments