Skip to content

[Fiber] Fix context propagation into Suspense fallbacks#36160

Merged
acdlite merged 1 commit intofacebook:mainfrom
acdlite:fallback-context-propagation-fix
Mar 27, 2026
Merged

[Fiber] Fix context propagation into Suspense fallbacks#36160
acdlite merged 1 commit intofacebook:mainfrom
acdlite:fallback-context-propagation-fix

Conversation

@acdlite
Copy link
Copy Markdown
Collaborator

@acdlite acdlite commented Mar 27, 2026

Summary

When a context value changes above a Suspense boundary that is showing its fallback, context consumers inside the fallback do not re-render — they display stale values.

propagateContextChanges, upon encountering a suspended Suspense boundary, marks the boundary for retry but stops traversing into its children entirely (nextFiber = null). This skips both the hidden primary subtree (intentional — those fibers may not exist) and the visible fallback subtree (a bug — those fibers are committed and visible to the user).

The fix skips the primary OffscreenComponent and continues traversal into the FallbackFragment, so fallback context consumers are found and marked for re-render.

In practice this often goes unnoticed because it's uncommon to read context inside a Suspense fallback, and when some other update (like a prop change) flows into the fallback it sidesteps the propagation path entirely. React Compiler makes the bug more likely to surface since it memoizes more aggressively, reducing the chance of an incidental re-render masking the stale value.

Test plan

  • Added regression test 'context change propagates to Suspense fallback (memo boundary)' in ReactContextPropagation-test.js
  • Verified the test fails without the fix and passes with it
  • All existing context propagation, Suspense, memo, and hooks tests pass

When a context value changes above a Suspense boundary that is showing
its fallback, context consumers inside the fallback do not re-render.
They continue to display stale values.

This happens because `propagateContextChanges`, upon encountering a
suspended Suspense boundary, marks the boundary for retry but stops
traversing into its children entirely (`nextFiber = null`). This skips
both the hidden primary subtree (intentional — those fibers may not
exist) and the visible fallback subtree (a bug — those fibers are
committed and visible to the user).

The fix skips the primary OffscreenComponent and continues traversal
into the FallbackFragment, so fallback context consumers are found and
marked for re-render.

In practice this often goes unnoticed because it's uncommon to read
context inside a Suspense fallback, and when some other update (like a
prop change) flows into the fallback it sidesteps the propagation path
entirely. React Compiler makes the bug more likely to surface since it
memoizes more aggressively, reducing the chance of an incidental
re-render masking the stale value.
@meta-cla meta-cla bot added the CLA Signed label Mar 27, 2026
@acdlite acdlite marked this pull request as ready for review March 27, 2026 21:53
@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label Mar 27, 2026
@react-sizebot
Copy link
Copy Markdown

Comparing: 2233b7d...20d7378

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.84 kB 6.84 kB +0.05% 1.88 kB 1.88 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 612.88 kB 612.91 kB = 108.30 kB 108.30 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.84 kB 6.84 kB = 1.88 kB 1.88 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 678.81 kB 678.85 kB = 119.26 kB 119.27 kB
facebook-www/ReactDOM-prod.classic.js = 698.20 kB 698.24 kB = 122.65 kB 122.65 kB
facebook-www/ReactDOM-prod.modern.js = 688.52 kB 688.55 kB = 121.03 kB 121.03 kB

Significant size changes

Includes any change greater than 0.2%:

(No significant changes)

Generated by 🚫 dangerJS against 20d7378

@acdlite acdlite requested review from eps1lon and gnoff March 27, 2026 22:01
@eps1lon eps1lon changed the title Fix context propagation into Suspense fallbacks [Fiber] Fix context propagation into Suspense fallbacks Mar 27, 2026
eps1lon
eps1lon approved these changes Mar 27, 2026
@acdlite acdlite merged commit 9627b5a into facebook:main Mar 27, 2026
245 checks passed
github-actions bot pushed a commit that referenced this pull request Mar 27, 2026
## Summary

When a context value changes above a Suspense boundary that is showing
its fallback, context consumers inside the fallback do not re-render —
they display stale values.

`propagateContextChanges`, upon encountering a suspended Suspense
boundary, marks the boundary for retry but stops traversing into its
children entirely (`nextFiber = null`). This skips both the hidden
primary subtree (intentional — those fibers may not exist) and the
visible fallback subtree (a bug — those fibers are committed and visible
to the user).

The fix skips the primary OffscreenComponent and continues traversal
into the FallbackFragment, so fallback context consumers are found and
marked for re-render.

In practice this often goes unnoticed because it's uncommon to read
context inside a Suspense fallback, and when some other update (like a
prop change) flows into the fallback it sidesteps the propagation path
entirely. React Compiler makes the bug more likely to surface since it
memoizes more aggressively, reducing the chance of an incidental
re-render masking the stale value.

## Test plan

- Added regression test `'context change propagates to Suspense fallback
(memo boundary)'` in `ReactContextPropagation-test.js`
- Verified the test fails without the fix and passes with it
- All existing context propagation, Suspense, memo, and hooks tests pass

DiffTrain build for [9627b5a](9627b5a)
github-actions bot pushed a commit that referenced this pull request Mar 27, 2026
## Summary

When a context value changes above a Suspense boundary that is showing
its fallback, context consumers inside the fallback do not re-render —
they display stale values.

`propagateContextChanges`, upon encountering a suspended Suspense
boundary, marks the boundary for retry but stops traversing into its
children entirely (`nextFiber = null`). This skips both the hidden
primary subtree (intentional — those fibers may not exist) and the
visible fallback subtree (a bug — those fibers are committed and visible
to the user).

The fix skips the primary OffscreenComponent and continues traversal
into the FallbackFragment, so fallback context consumers are found and
marked for re-render.

In practice this often goes unnoticed because it's uncommon to read
context inside a Suspense fallback, and when some other update (like a
prop change) flows into the fallback it sidesteps the propagation path
entirely. React Compiler makes the bug more likely to surface since it
memoizes more aggressively, reducing the chance of an incidental
re-render masking the stale value.

## Test plan

- Added regression test `'context change propagates to Suspense fallback
(memo boundary)'` in `ReactContextPropagation-test.js`
- Verified the test fails without the fix and passes with it
- All existing context propagation, Suspense, memo, and hooks tests pass

DiffTrain build for [9627b5a](9627b5a)
acdlite pushed a commit to vercel/next.js that referenced this pull request Mar 28, 2026
[diff
facebook/react@3cb2c420...9627b5a1](facebook/react@3cb2c42...9627b5a)

<details>
<summary>React upstream changes</summary>

- facebook/react#36160
- facebook/react#35701
- facebook/react#35627
- facebook/react#35621
- facebook/react#35623

</details>

---------

Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed React Core Team Opened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants