react-use icon indicating copy to clipboard operation
react-use copied to clipboard

useEffectOnce render twice with React.StrictMode

Open gomes042 opened this issue 3 years ago • 5 comments

https://stackoverflow.com/questions/48846289/why-is-my-react-component-is-rendering-twice

Maybe a solution:

import {EffectCallback, useEffect, useRef} from 'react';

function useEffectOnce(effect: EffectCallback) {
  const destroyFunc = useRef<void | any>();
  const calledOnce = useRef(false);
  const renderAfterCalled = useRef(false);

  if (calledOnce.current) {
    renderAfterCalled.current = true;
  }

  useEffect(() => {
    if (calledOnce.current) {
      return;
    }

    calledOnce.current = true;
    destroyFunc.current = effect();

    return () => {
      if (!renderAfterCalled.current) {
        return;
      }

      if (destroyFunc.current) {
        destroyFunc.current();
      }
    };
  }, []);
}

export default useEffectOnce;

gomes042 avatar May 01 '22 00:05 gomes042

I believe that this will only happen in development, not production which is by design with React 18 StrictMode changes.

AlexJeffcott avatar May 04 '22 20:05 AlexJeffcott

I believe that this will only happen in development, not production which is by design with React 18 StrictMode changes.

Yes, definitely. But this being handled can avoid some problems for use cases of StrictMode in development (even not present in production). If the hook name is useEffectOnce and there is a way to "ignore this strictmode problem" why not?

gomes042 avatar May 04 '22 23:05 gomes042

This is affecting useThrottle too in #2343.

jpwilliams avatar Jun 04 '22 11:06 jpwilliams

The very reason it happens in dev mode using <StrictMode> in React 18 is because it can happen in production mode. One way to look at it is that without upgrading useEffectOnce, react-use becomes a React 17 library.

ghost avatar Aug 17 '22 10:08 ghost

Any chance we can get this fix into the library? I know there haven't been updates in a year or so but would love to see it work.

This simple implementation is what I'm using in my code:

  const isLoaded = useRef(false);

  useEffectOnce(() => {
    console.log('(from the hooks lib, this does run twice in strict mode)!');
  });

  useEffect(() => {
    if (isLoaded.current === false) {
      isLoaded.current = true;
      console.log('this only happens once, truly!');
    }
  })

andrewmartin avatar Dec 17 '23 18:12 andrewmartin