Skip to content

Bug: Nondeterministic first render #21059

@bennettdams

Description

@bennettdams

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

See on CodeSandbox

enter image description here

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:

  1. the "normal" way
  2. with a "mounted" render hack
  3. 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)?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Status: UnconfirmedA potential issue that we haven't yet confirmed as a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions