5

another React 18 strict mode question. I'm aware React will call the render and effect functions twice to highlight potential memory leaks with the upcoming features. What I yet don't understand is how to properly handle that. My issue is that I can't properly unmount the first render result as the two useEffect calls are performed with the state of the 2nd render. Here is an example to showcase what I mean.


  const ref = useRef(9);
  const id = useId();

  console.log('@@ initial id', id);
  console.log('@@ initial ref', ref.current);

  ref.current = Math.random();

  console.log('@@ random ref', ref.current);

  useEffect(() => {
    console.log('@@ effect id', id);
    console.log('@@ effect ref', ref.current);

    return () => {
      console.log('@@ unmount id', id);
      console.log('@@ unmount ref', ref.current);
    };
  });

and here is the log output

@@ initial id :r0:
@@ initial ref 9
@@ random ref 0.26890444169781214
@@ initial id :r1:
@@ initial ref 9
@@ random ref 0.7330565878991766
@@ effect id :r1:                 <<--- first effect doesn't use data of first render cycle
@@ effect ref 0.7330565878991766
@@ unmount id :r1:
@@ unmount ref 0.7330565878991766
@@ effect id :r1:
@@ effect ref 0.7330565878991766

As you can see there is no useEffect call with the state of the first render cycle and as well the 2nd render cycle doesn't provide you with the ref of the first render cycle (it is initialized with 9 again and not 0.26890444169781214. Also the useId hook returns two different ids where the 2nd Id is kept also in further render cycles. Is this a bug or expected behavior? If it is expected, is there a way to fix this?

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
fragsalat
  • 500
  • 4
  • 14
  • it seems to be doing two render cycles before the effect, and then doing a effect cleanup without running the render cycle, looks like dev stritct mode is broken :P, it runs only once in production, but the explanation for dev stritct mode does not match this output – Azzy Nov 28 '22 at 09:42
  • It's unclear what you are trying to do, but *"if remounting breaks the logic of your application, this usually uncovers existing bugs. From the user’s perspective, visiting a page shouldn’t be different from visiting it, clicking a link, and then pressing Back.*". See the answer to https://stackoverflow.com/questions/72238175/useeffect-is-running-twice-on-mount-in-react – Youssouf Oumar Nov 29 '22 at 19:02

1 Answers1

0

Before React 18's StrictMode, your components would only mount once. But now, they mount, are unmounted, and then remounted. So its not only the effects that are ran twice - your entire component is rendered twice.

That means your state is re-initialized and your refs are also reinitialized. Obviously, your effects will run twice as well.

About effects running twice, you need to properly cleanup async effects - any effect that does something asynchronously, like fetching data from the server, adding an event listener etc. Not all effects need a cleanup.

Also, the effects are meant to run twice in development (they only run once in production). Some people try to prevent effects from running twice, but that is not okay. If you cleanup an effect properly, there should be no difference in its execution when it runs once in production or twice in development.

Also the useId hook returns two different ids where the 2nd Id is kept also in further render cycles. Is this a bug or expected behavior? If it is expected, is there a way to fix this? The second value will be the one that is used. It is not a bug, and you can keep using that as the "true" value.

You can readup more on StrictMode here.

Edit: Detecting an unmount.

// Create a ref to track unmount
const hasUnmounted = useRef(false)

useEffect(() => {
  return () => {
    // Set ref value to true in cleanup. This will run when the component is unmounted. If this is true, your component has unmounted OR the effect has run at least once
    hasUnmounted.current = true;
  }
}, [])
Ali Nauman
  • 1,374
  • 1
  • 7
  • 14
  • 2
    This just repeats what is written on the web and doesn't help me. Thanks anyways. I still have effects which doesn't match the render cycle. – fragsalat Nov 28 '22 at 09:28
  • Let me rephrase this, when React is now mounting, unmounting and re-mounting the component. How can I detect the unmount? – fragsalat Nov 28 '22 at 09:54
  • Added a snippet for this – Ali Nauman Nov 28 '22 at 11:29
  • Yes, but this snippet will now only work in development and not in production. – Thomas Nov 28 '22 at 11:51
  • True, but like I mentioned in my answer, if you're handling your effect correctly, it should work the same way in both production and development. In the snippet from the question, the only thing being done in the effect is some logging - that requires no clean up. Take a look here on how to fix a real effect: https://beta.reactjs.org/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development – Ali Nauman Nov 28 '22 at 13:34
  • 1
    detecting the unmount will not work as I would expect it. The function component is called twice and two new refs are initialized. Then the useEffect will be called, destroy fn will be called and useEffect will be called again. Both useEffect cycles will use the second ref created. Thus the first render cycle is completely hidden and anything created there is lost and no corresponding effect call is performed. – fragsalat Nov 28 '22 at 13:55