-
Notifications
You must be signed in to change notification settings - Fork 49.4k
Description
It is possible that this is not a bug, but rather expected - I couldn't find any material on the internet for this particular use case though.
React version: v17.0.1
TL;DR React sometimes renders a loading state and sometimes not, without changes in the UI. Maybe this has something to do with batched updates, where the initial state and the next state are batched?
Steps To Reproduce
Here's the setup, a chart that takes a long time to render. So long that the render is blocking. There are three different ways to render the chart here:
- the "normal" way
- with a "mounted" render hack
- with the same "mounted" render hack, but with an additional
setTimeout
Option 2 & 3 both have a small useState
to check whether they've been mounted. I do this to show a "Loading" state conditionally:
function ChartWithMountHack({ data }: { data: Data }) {
// initially not mounted
const [isMounted, setIsMounted] = useState<boolean>(false);
useEffect(() => {
// "Now I've been mounted!"
setIsMounted(true);
}, []);
return !isMounted ? <p>Loading</p> : <Chart data={data} />;
}
I did this, because I want to show a "Loading" state instead of a blocking render, so e.g. page switches or ternary rendering (e.g. hasData ? <p>No data</p> : <Chart />
) are shown immediately, instead of blocking. (If there are better ways, please let me know!)
The current behavior
Now, each button will render one of the three options/charts. Again, the second and third chart have a small hack to check whether they're mounted or not.
Try clicking on the first button and the second button back & forth quickly.
You will see that sometimes the "Chart with mount hack" will ("correctly") render the "Loading" state, but sometimes it just doesn't render the "Loading" - instead it blocks the render up until the chart is finished rendering (skips the "Loading" state).
I think this is due to the render cycles and whether you get the two updates in one cycle of the batching. (first: isMounted === false
-> second: isMounted === true
)
I can't really tell how to reproduce this, hence the "nondeterministic" in the title. Sometimes you also have to click on "Regenerate data" and click back & forth after that.
The expected behavior
Option 3 ("Chart with mount hack with timeout") ALWAYS gives me the "Loading" state, which is exactly what I want. The only difference to option 2 is using a setTimeout
in the useEffect
where isMounted
is set to true. setTimeout
is used here to break out of the update batching.
Is there a better way to opt-out of the batching, so isMounted
will always render with its initial value (false
)?
Shouldn't the first render and its initial values be outside of the batched updates? If not, how can I tell React to do so (given that stuff like async/setTimeout
/.. are just "hacks" to circumvent that right now)?