Let's say I have the following code:
function MyComponent() {
const [status, setStatus] = useState('status_a')
const [socket, setSocket] = useState()
useEffect(() => {
const socket = new WebSocket('ws://foo.com')
socket.onclose = () => console.log(`socket closed while status was ${status}`)
setSocket(socket)
}, [])
// ... some code that changes status in response to user input
}
So there's a bug here, because when socket.onclose fires, status will always be the initial value, "status_a".
One solution, using refs:
function MyComponent() {
const [status, setStatus] = useState('status_a')
const [socket, setSocket] = useState()
const callbacks = useRef({})
useEffect(() => {
const socket = new WebSocket('ws://foo.com')
socket.onclose = () => callbacks.current.socketOnClose()
setSocket(socket)
}, [])
// ... some code that changes status in response to user input
// this gets redefined on every render, so it always has the new value of status
callbacks.current.socketOnClose = () => {
console.log(`socket closed while status was ${status}`)
}
}
It works, but smells a bit weird to me. Any better ideas?
This example only has one state dependency in the callback, but the real code has many dependencies, so setting up a change handler for each one that redefines the callback would also be clunky.
I think using a
refis the right solution, but I just don't think you should use it for the callback, but for the values you want the callback to have access to:Why? Let's see what the React documentation says:
This is similar to your case where your
onclosecallback, and therefore theuseEffectthat contains it, both have a dependency on some data (status), but you do not want a change on that data to re-run youruseEffectand socket setup logic.