Fix renderDataHook mount effects when the first render suspends - #4099
Conversation
A suspending first render inside synchronous act() drops React's passive-effect task, so provider mount effects never run. Schedule one sibling setState after mount so React flushes those effects. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
🦋 Changeset detectedLatest commit: 734291d The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
Size Change: 0 B Total Size: 103 kB ℹ️ View Unchanged
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4099 +/- ##
=======================================
Coverage 97.84% 97.84%
=======================================
Files 156 156
Lines 3057 3057
Branches 612 612
=======================================
Hits 2991 2991
Misses 18 18
Partials 48 48 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
ntucker
left a comment
There was a problem hiding this comment.
Staff Reviewer: LGTM with FOLLOW_UP
Kick is the right fix: sync act() + first-render use() drops passive effects (DataStore/initManager, and the #4097 hold replay). Empty act is insufficient; awaiting mount act would rewrite suspend-first tests. No other change-this-PR issues.
FOLLOW_UP (non-blocking): add a minimal regression that fails without Kick and passes with it — e.g. first-render suspend via use(useFetch()), then assert manager init / mount effects ran before any later React-scheduled update. Do not assert only “fetch started”: FETCH already runs in middleware during render without Kick. Existing suites staying green on master does not lock Kick until #4097’s hold makes useFetch-use.web.tsx the integration canary.
Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Motivation
renderDataHook()mounts inside a synchronousact(), the same way React Testing Library'srenderHookdoes. When that first render suspends onuse()—use(useFetch())is the case that shows it — React 19 commits the tree and then drops the task that runs passive effects. It warns that a component suspended inside anactscope that was not awaited.DataStorehas committed, but its mount effect does not run. On master the nextrdc/setresponsehappens to schedule a React update, and that update flushes the stranded effects, so the existing suites still pass. Anything that keeps that dispatch off the React queue leaves the effects stranded: the fetch waits on the mount effect, and the mount effect waits on a React task.An empty
act(() => {})after mount does not help. React only flushes the stranded effects when it has a real update to render. Awaitingactaround the mount would also flush them, and it would resolve data before the first assertion, which rewrites every test that suspends first.Solution
After
renderHookreturns,makeRenderDataHook()schedules onesetStateinsideacton a sibling<Kick />that rendersnull. The sibling commits even when the hook suspends, and that update gives React a task that runs the stranded passive effects.result.currentis still unset when the first render suspends, so tests keep their suspend-then-resolve shape.@data-client/testhas its own changeset.Relative imports of
packages/testresolve todist/index.js. Rebuild@data-client/test(build:libandbuild:bundle) so those tests run the new helper.Verification
yarn test --selectProjects ReactDOM --selectProjects Node --testPathPatterns 'packages/(react|core|use-enhanced-reducer)/'a76996a5ed