Skip to content

fix: support React 19.1+ by waiting for effects in render - #17

Closed
ben-cook wants to merge 2 commits into
testing-library:mainfrom
ben-cook:ben/chore/react-19-3
Closed

ben-cook wants to merge 2 commits into
testing-library:mainfrom
ben-cook:ben/chore/react-19-3

Conversation

@ben-cook

Copy link
Copy Markdown
Contributor

Problem

Since React 19.1, passive effects (useEffect, and the subscription in useSyncExternalStore) can run in a later task than their commit. await render() used to resolve after effects had run. Now it can resolve before, so a test that triggers an update straight after rendering fires it before the component has subscribed. 14 tests in this repo failed this way after upgrading to React 19.3.1.

Change

  • render and rerender from createRenderStream (and so renderHookToSnapshotStream) now resolve only after the effects of that commit have run. The internal Wrapper has a useEffect, and parent effects run after child effects. The existing tests pass unchanged.
  • Dev dependencies move to React 19.3.1 and @types/react 19. The React 16/17 legacy root is still supported at runtime and is now typed locally, because React 19 types removed ReactDOM.render.
  • The Symbol.dispose test polyfill no longer crashes on Node 24, where the symbol is built in and read-only.

renderWithoutAct is unchanged, because it never waited for the render either.

Testing

yarn validate passes on React 19.3.1. The test suite also passes on React 19.0.0 and 18.3.1.

React 19 types no longer declare `ReactDOM.render` or
`unmountComponentAtNode`, so type the React 16/17 code path locally.
Node 24 ships a read-only `Symbol.dispose`, so only polyfill it when
missing.
Since React 19.1, effects can run in a later task than their commit,
so code that subscribes in `useEffect` was not yet subscribed when
`await render()` resolved.
@phryneas

Copy link
Copy Markdown
Member

Thank you for the PR and bringing this to my attention!

We'll need to also fix this for takeSnapshot, so I'll use a different way of implementing this . I'm working on that over in #18.

Closing this here. Thank you again!

@phryneas

Copy link
Copy Markdown
Member

I'm cherry-picking your "version bump" commit to #19 so you should end up as a contributor, too :)

phryneas added a commit that referenced this pull request Oct 27, 2026
…xt` promises resolve after effects are run (#18)

As reported in #17, since React 19.1, passive effects (useEffect, and
the subscription in useSyncExternalStore) can run in a later task than
their commit.

That means that with the current approach, `render/rerender` or even
`takeNext` could trigger before all effects for that render had finished
processing.

This switches over to `onPostCommit`, an api that is not part of the
official React types, but has been around stable since React 18, based
on [this React RFC](https://github.com/reactjs/rfcs/pull/139/changes)
and implemented in react/react#17910.

React 16.8+ and 17 retain existing behaviour.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants