Dave Irvine
Dave Irvine

Reputation: 416

Equivalent isFirstRender hook for StrictMode in development mode

Previously I've used a hook like this to determine if a component is being rendered for the first time:

function useIsFirstRender() {
  const isFirst = useRef(true);

  if (isFirst.current) {
    isFirst.current = false;

    return true;
  }

  return isFirst.current;
}

function App() {
  const isFirstRender = useIsFirstRender();

  useEffect(() => {
    if (isFirstRender) {
      // It's the first render
    }
  }, []); // I'd expect [] to cause useEffect's first param to be called once, but with StrictMode this doesn't happen.
}

Since starting to use StrictMode in React 18, this hook no longer functions as I expect, as my component is being rendered twice due to how StrictMode works in development, and isFirstRender is true on both renders. Instead I'm having to implement useRef directly in each of my components as per https://github.com/reactwg/react-18/discussions/18

function App() {
  const isFirstRender = useRef(true);

  useEffect(() => {
    if (isFirstRender.current === true) {
      isFirstRender.current = false;

      // It's the first render
    }
  }, []);
}

I'm looking to make another re-usable hook so that I don't need to reimplement the useRef logic in every component where I need this behaviour.

I'm aware that this double rendering behaviour only happens in development mode. However some of the actions that I want to take only on the first render involve calling an API that isn't idempotent, so during development this is making things very difficult.

Upvotes: 0

Views: 887

Answers (2)

Istvan Tabanyi
Istvan Tabanyi

Reputation: 886

What about this kind of useFirstRender hook? This can be reused in every component, its callback called only at the first render. After unmount it also called again once at remount.

Update 1: For clarification, this actually run on second render, because I think the first render added by strict mode is not what you are interested in, but obviously it can be modified to run on the first render of react 18 strict mode and ignore the second one. I don't know which one makes more sense for you.

Update 2: I don't know why I thought it is good idea to run on second run honestly, as the additional run is the second one, based on reactwg discussion.I changed it to run on actual first render, and not more:) If unmounted then remounted it again triggers the callback only once.

function useFirstRender(callback) {
  const isFirst = useRef(false);

  if (!isFirst.current) {
    isFirst.current = true;
  }

  useEffect(() => {
    if (isFirst.current) {
      callback();
    }
    return () => {
      isFirst.current = false;
    };
  }, []);
}

function Component() {
    useFirstRender(() => {
        console.log('Rendered first time');
    });
    return <div>Component</div>;
}

function App() {
    return (
        <React.StrictMode>
            <Component/>
        </React.StrictMode>
    );
}

Upvotes: 4

Daniele Ricci
Daniele Ricci

Reputation: 15837

This sounds to be a strange question since passing an empty array as second argument of useEffect has exactly the effect you are asking for, requiring nothing more.

Could it be that the problem is not the code runs twice, but your component is unmounted and mounted once again?

I suggest you to add following code to your component just to detect if that's the case:

function App() {
  // Add this
  let counterA = 0;
  let counterB = 0;
  useEffect(() => {
    console.log("counterA", counterA++);

    return () => console.log("counterB", counterB++);
  }, []);

  // Follows the rest of your code
}

In my console I can see only counterA 0 and later counterB 0 when the component is unmounted.

If you see counterA 1, counterA 2, etc... the problem is outside of my knowledge and you can ignore this answer; but if you can see counterA 0 and counterB 0 multiple times, React.js works as intended and probably you have to rethink about the way you control how and when your API is called; Context may help you.

The reason why React.js in StrictMode and in development environment runs some code twice (as setState function) is exactly to help us detecting bad implementations. Seeking for way to avoid this barrier right with the purpose to be free to do bad implementations sounds as a very bad idea. ;)

Edit: I updated the App I used to test this to the latest versions and latest best practices and (only in development environment) I see counterA 0, counterB 0 and counterA 1 in rapid sequence: it's time to refactor some of my code too!

Upvotes: 1

Related Questions