DEV Community

React 18 - Avoiding Use Effect Getting Called Twice

Alan Richardson on April 21, 2022

Update - Not Recommended AG Grid no longer uses this approach or recommends that you use this Hook either. Instead ensure that your Reac...
Collapse
 
chiubaca profile image
Alex Chiu

Using this trick should be avoided and there is probably a better way. By opting in to use this trick you risk your application to break in production at a later date when React releases a feature called "Reusable state" Reusable state will allow for unmounting and remounting of components without losing state. React 18 introduces the double firing of useEffect in dev mode to prepare us for this upcoming change and stress test your components.

If your component is not behaving correctly in dev mode now. It wont behave correctly in production later!

reference - reactjs.org/blog/2022/03/29/react-...

It's important to update our mental models of how useEffect works from React 18 onwards as it is now subtly different. This video I found hugely helpful

youtube.com/watch?v=MXSuOR2yRvQ

Collapse
 
johnsonmercyi profile image
johnsonmercyi

Thanks for sharing the video link. It's really helpful.

Collapse
 
joelnet profile image
JavaScript Joel

Probably just a minor copy/paste bug, but there is an extra open parenthesis here that doesn't have a matching close below.

export const useEffectOnce = ( effect => {
Enter fullscreen mode Exit fullscreen mode

I am also seeing a problem when Strict mode is not enabled or app is build in production mode, the unmount event will not fire.

Here's my code for testing:

export const MyComponent = () => {
  useEffect(() => {
    console.log("useEffect enter");

    return () => {
      console.log("useEffect exit");
    };
  }, []);

  useEffectOnce(() => {
    console.log("useEffectOnce enter");

    return () => {
      console.log("useEffectOnce exit");
    };
  });

  return <b>Hi</b>;
};
Enter fullscreen mode Exit fullscreen mode

Output in production mode when mounting and unmounting the component once.

useEffect enter
useEffectOnce enter
useEffect exit
Enter fullscreen mode Exit fullscreen mode

useEffectOnce exit was expected to fire here.

Collapse
 
eviltester profile image
Alan Richardson

Thanks, we've updated the code to fix typos and hopefully addressed Jack's point below.

Collapse
 
jherr profile image
Jack Herrington

useEffectOnce exit is not going to fire as far as I can see. This hook works to defeat the hook being called twice, but the cleanup functions will never get called as far as I can see.

I honestly don't know how to fix this. I've been trying everything I can think of.

Collapse
 
eviltester profile image
Alan Richardson

Thanks for pointing this out Jack. Niall updated the code, hopefully it is better.

Collapse
 
mike7petrusenko profile image
Mike

What is the difference between development and production hook behavior? Could you please explain.

Collapse
 
skydiver profile image
Martin M.
Collapse
 
iamyoki profile image
Yoki

Final code like this might be better.

function useEffectOnce(effect) {
  const effectFn = useRef(effect)
  const destroyFn = useRef()
  const effectCalled = useRef(false)
  const rendered = useRef(false)
  const [, refresh] = useState(0)

  if (effectCalled.current) {
    rendered.current = true
  }

  useEffect(() => {
    if (!effectCalled.current) {
      destroyFn.current = effectFn.current()
      effectCalled.current = true
    }

    refresh(1)

    return () => {
      if (rendered.current === false) return
      if (destroyFn.current) destroyFn.current()
    }
  }, [])
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
sunil_prasad1 profile image
Sunil Prasad

This happens only in development mode not in production mode . So should we change the code to handle behaviour only for the development mode.

Collapse
 
ecyrbe profile image
ecyrbe • Edited

No, this can happen in production. It's just that they do it on purpose on dev. But in production, it can happen. They did not activate this on dev just to piss devs off.