skip to Main Content

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


  1. 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:

    useEffect(() => {
    ref.current = cb
    }, [cb])
    

    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.

    const handler = () => { ref.current() }
    
    Login or Signup to reply.
  2. You can assign the functional return value of useCallback directly to ref.current (and omit the intermediate variable cb entirely) like this:

    Code in the TypeScript Playground

    import { useCallback, useEffect, useRef } from "react";
    
    function ExampleComponent({ bar, baz, fn, foo, events }) {
      const ref = useRef();
    
      ref.current = useCallback(
        () => void fn(foo, bar, baz),
        [fn, foo, bar, baz],
      );
    
      useEffect(() => {
        events.on("the-event", () => void ref.current());
        return () => events.off("the-event");
      }, [events]);
    
      return null;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search