Skip to content

Bug: createRoot().render() is not fully synchronous unless wrapped in flushSync, unlike legacy ReactDOM.render #32811

@jukben

Description

@jukben

React version:
18.2.0 (reproduced in latest 19.1 as well)


Summary

There is an open issue proposing documentation updates around the use of flushSync with createRoot().render() to enforce synchronous rendering. However, since that issue has gone without feedback for several months, and given the core behavior observed, I suspect this may be an actual bug in React rather than just a documentation gap. I'm opening this issue here in the main React repository to clarify whether this behavior is intentional or not.


Steps To Reproduce

  1. Render a basic React app using createRoot(...).render(<App />).
  2. Inside <App />, log messages from useLayoutEffect and useEffect.
  3. Log messages before and after root.render() call.
  4. Compare behavior between:
    • createRoot(...).render(...) (modern API)
    • flushSync(() => root.render(...))
    • Legacy ReactDOM.render(...)

Link to code example: CodeSandbox


The current behavior

Using createRoot(...).render(...) without wrapping in flushSync, we observe that:

  • The log inside useLayoutEffect is emitted after console.log("Post root.render"), indicating the effect happens after the render call resolves.
  • In contrast, using flushSync(() => root.render(...)) or legacy ReactDOM.render(...), useLayoutEffect is called before the post-render log.

This suggests the modern root API allows post-render behavior to interleave with layout effects, even outside of explicitly opted-in concurrent features. This might violate expectations around synchronous behavior of the initial render.


The expected behavior

As @rickhanlonii noted in a related discussion, the initial render should be synchronous by default. If that’s the case, this behavior seems inconsistent.

  • createRoot(...).render(...) should preserve layout effect timing consistency with legacy ReactDOM.render(...), without needing to wrap in flushSync.
  • If the current behavior is intentional, it would help to explicitly document that createRoot(...).render(...) may be async under certain conditions—even without concurrent features being enabled.

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