Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Suggestion]: StrictMode confusion about ordering of the events #7315

Open
Tosheen opened this issue Nov 29, 2024 · 6 comments
Open

[Suggestion]: StrictMode confusion about ordering of the events #7315

Tosheen opened this issue Nov 29, 2024 · 6 comments

Comments

@Tosheen
Copy link

Tosheen commented Nov 29, 2024

Summary

This thread comes really close to avoid repeating this question again.
However I still have some doubts about the chain of the events in the strict mode.

I will quote the answer which provide the most valuable feedback.

"The mental model for this is that React really does unmount the component, but when re-mounting the component we restore the previously used state instead of starting fresh, the way Fast Refresh does, or some future features will."

But if that's the case why the order of the events(rendering, setting state, effects etc) is different while running in strict mode and outside of it?

Here are the concrete examples:

  1. https://codesandbox.io/p/sandbox/2z4wq2. This sandbox runs inside a strict mode and by inspecting the console we can see that the order of the events is like this
render
state setter
render
state setter
effect
clean up
effect

The second render and state setter are dimmed out according to the documentation and that's fine. So at least on the surface it appears that react is triggering render function (and state setter) 2 times before moving to the effects.

  1. https://codesandbox.io/p/sandbox/93zfkw. This sandbox runs outside of the strict mode and there is a button to toggle the mounting of the component. If the component is mounted, unmounted and mounted again the order of the events is like this.
render
state setter
effect
clean up
render
state setter
effect

This example has the mental model I have always worked with and which is clear, so I would waste too much time on it since that's how React also behaves on production.

The question is, if react is really performing mounting, unmounting and remounting while is the order different?

To be honest I haven't noticed that this difference is making my code harder to reason about while working with StrictMode.
I would like to know the details, if possible. What does this approach help React achieve that original one does not?

Page

https://react.dev/reference/react/StrictMode

Details

I think it would be helpful to update the page to explain why the behavior in StrictMode is different than simply running mounting, unmounting and mounting again.

@pamellix
Copy link

pamellix commented Dec 7, 2024

@Tosheen, good day!

useEffect in StrictMode works a little differently. In StrictMode, the React does mount, unmount, and remount the component again, but useEffect is called at another stage of the React algorithm and it has "its own life", however, it is also called, immediately cleared and called again. This is done in order to properly track the moments when some logic was performed in useEffect, but the user has already left the page where this component was.

For example:

import { useEffect } from "react";

export default function App() {
    useEffect(() => {
        const onChange = () => console.log("resize", onChange)

        window.addEventListener("resize", onChange)
    }, [])

    return <div>test</div>
}

In this code, we subscribe to the "resize" event, but we don't unsubscribe from it anywhere, and StrictMode, thanks to this "trick", points us to this problem before we could notice it. To solve this problem, we need to modify the following code

useEffect(() => {
        const onChange = () => console.log("resize", onChange)

        window.addEventListener("resize", onChange)
        
        return  () => {
            window.removeEventListener("resize", onChange)
        }
    }, [])

This also applies to http requests that are sent to useEffect. Strict Mode will call requests twice after all stages of useEffect processing, but this is done for a reason, since there are often cases when the request was sent, but the user has already left the page, the data has returned - but the component is no longer there. To avoid such problems, StrictMode will handle useEffect separately and point out this error by calling the request twice. If you ignore this warning, in the case that I described above, the following problem may appear:

image

To solve this problem, you should use data processing after calling fetch or use an AbortController

In conclusion, I want to say that StrictMode also works correctly, and in general does not violate its concept of "first mount" -> "unmount component" -> "second mount", just useEffect is called at another stage of processing the algorithm, which is why with StrictMode, useEffect processing goes at the very end, and with "manual rewire" - in the middle of the process

Hope this helps!

@branko-toshin-deel
Copy link

Hi, @pamellix.
I do get why having a double mount helps with detecting bugs earlier in the development stage and you examples are clear enough. However it still doesnt explain why the order is different.
Wouldnt the outcome be the same if effects ar run at the same stage they are run in non strict mode?
For example if I manually mount/unmount/mount component I would be able to catch the effect bugs like not clearing the timeouts etc...

@pamellix
Copy link

pamellix commented Dec 10, 2024

@branko-toshin-deel To be honest, I do not know why the StrictMode logic is implemented in this way, but I assume that this is done in order to optimize the application in dev mode. useEffect starts executing between the second and third stages of React rendering, when the basic manipulations with vDOM and DOM have already been performed. I assume this was done with the intention of not overloading StrictMode's work with unnecessary operations.

I think that with StrictMode React, when rendering for the first time, it only works with what changes the DOM, skipping the stages of working with effects. And after the second render, it already fully processes all the effects that are in the component, thereby reducing the load. Roughly speaking, if I understand everything correctly, with StrictMode we have a "one and a half" render (that is, the first render with skipping effects, and the second render with processing effects twice) of the component, and with "manual re-render" - all two re-renders.

I also think that the React team did not introduce such a detail into the documentation because the developers pay little attention to it, more precisely, when working with StrictMode, this detail should not ruin the operation of the application, but I will try to make my contribution to the StrictMode documentation in order to describe in detail its logic of operation!

P.S. I also assume that this is done so that working with StrictMode feels not just like "two renderers", but like "one, but an extended render". That is, roughly speaking, StrictMode works with each stage separately, and does not just re-render components twice, thereby it retains the primary logic of the React render, with all stages. I guess StrictMode tests the component in stages not the way we think (I use numbers to indicate the stages of rendering)

 "1 -> 2 -> 3 -> 1 -> 2 -> 3"

it tests them like this

"1 -> 1 -> 2 -> 2 -> 3 -> 3" 

(just my guess)

@branko-toshin-deel
Copy link

@pamellix
Yes thats the thing we can do now, to guess :). I would like to know the details behind that decision and what does it help achieve.

@pamellix
Copy link

@branko-toshin-deel
I think in the next few days I will look at the StrictMode source code and try to see in detail how it works, post comments of interesting points in this issue that I find and add to the documentation on React.dev

@branko-toshin-deel
Copy link

@pamellix That would be most appreciated.
Thanks for the effort.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants