I have a scenario that I keep running into: I have an expensive function with a callback that’s wrapped in a useEffect. In this case, subscribing to a broadcast. I’d like to avoid re-subscribing every time the callback closure changes, and I was wondering how to do that properly.
For example, events.on/off
get called every time a state changes:
const cb = useCallback( () => {
fn( foo, bar, baz )
}, [fn, foo, bar, baz])
useEffect( () => {
events.on( "the-event", () => {
cb()
})
return () => events.off("the-event")
}, [events, cb])
Can I do something like the below with useRef to avoid re-subscribing:
const cb = useCallback( () => {
...
}, [fn, foo, bar, baz])
const ref = useRef(cb)
useEffect( () => {
events.on( "the-event", () => {
ref.current()
})
return ...
}, [events, ref])
2
Answers
I believe the useRef will work for what you are wanting to do. However I would wrap the ref assignment in its own useEffect so it stays up to date and we call the right version whenever we do call it. You can also remove ref from the dependency array of the useEffect you have. Since it is stable it shouldn’t need to be in there.
This could be the useEffect you use to assure the reference to the callback stays up to date:
Along with this I would use a handler that calls the ref.current() instead of passing it directly. I believe passing the current will cause the same issues as previously with passing the cb itself. The handler should just be within the useEffect and can just be an arrow function that calls the cb function.
You can assign the functional return value of
useCallback
directly toref.current
(and omit the intermediate variablecb
entirely) like this:Code in the TypeScript Playground