Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's a good pattern for invoking event handler props in useEffect?

Let's assume a component:

const Foo = ({id, onError}) => {
    useEffect(() => {
        subscribe(id).catch(error => onError(error));
        return () => cleanup(id);
    }, [id, onError]);

  return <div>...</div>;
}

The idea is simple-- run an effect that subscribes using the current "id". If the subscription fails, invoke an event handler onError that is passed down as a prop.

However, for this to work correctly, the onError prop that's passed down must be referentially stable. In other words, if a consumer of my component tried the following, they may run into problems where the effect is run for each render:

const Parent = () => {
   // handleError is recreated for each render, causing the effect to run each time
   const handleError = error => {
     console.log("error", error);
   }

   return <Foo id="test" onError={handleError} />
}

Instead, they would need to do something like:

const Parent = () => {
   // handleError identity is stable
   const handleError = useCallback(error => {
     console.log("error", error);
   },[]);

   return <Foo id="test" onError={handleError} />
}

This works, but I'm somehow unhappy with it. The consumers of Foo need to realize that onError must be stable, which is not something that's plainly obvious unless you look at its underlying implementation. It breaks the component encapsulation and consumers can easily run into problems without realizing it.

Is there a better pattern to manage props like event handlers that may be invoked within useEffect?

like image 997
AMS Avatar asked Jan 25 '26 16:01

AMS


1 Answers

You need to remove onError from your dependency list, but still call if it changes. For that you can use a ref, and update it via useEffect on each render.

You can also use optional chaining ?. to avoid invoking the function if it's undefined.

const Foo = ({ id, onError }) => {
  const onErrorRef = useRef(); 
  
  useEffect(() => {
    onErrorRef.current = onError;
  });

  useEffect(() => {
    subscribe(id).catch(error => onErrorRef.current?.(error));
    return () => cleanup(id);
  }, [id]);

  return <div>...</div>;
}
like image 169
Ori Drori Avatar answered Jan 27 '26 04:01

Ori Drori



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!