diff --git a/compiler/CLAUDE.md b/compiler/CLAUDE.md index 0fade42b2bf6..877c74a8a5d4 100644 --- a/compiler/CLAUDE.md +++ b/compiler/CLAUDE.md @@ -76,7 +76,7 @@ yarn snap minimize --update ## Version Control -This repository uses Sapling (`sl`) for version control. Sapling is similar to Mercurial: there is not staging area, but new/deleted files must be explicitlyu added/removed. +This repository uses Sapling (`sl`) for version control. Sapling is similar to Mercurial: there is not staging area, but new/deleted files must be explicitly added/removed. ```bash # Check status diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts index 1b2a72271127..4cdbb6aea6c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts @@ -513,7 +513,7 @@ function inferBlock( if (handlerParam != null) { CompilerError.invariant(state.kind(handlerParam) != null, { reason: - 'Expected catch binding to be intialized with a DeclareLocal Catch instruction', + 'Expected catch binding to be initialized with a DeclareLocal Catch instruction', loc: terminal.loc, }); const effects: Array = []; @@ -1315,7 +1315,7 @@ class InferenceState { #values: Map; /* * The set of values pointed to by each identifier. This is a set - * to accomodate phi points (where a variable may have different + * to accommodate phi points (where a variable may have different * values from different control flow paths). */ #variables: Map>; diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts index efa8975a78af..cbc973efa8d1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts @@ -143,7 +143,7 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void { } /* - * Validate that all scopes have properly intialized, valid mutable ranges + * Validate that all scopes have properly initialized, valid mutable ranges * within the span of instructions for this function, ie from 1 to 1 past * the last instruction id. */ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/Logger-test.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/Logger-test.ts index ca386fb2402e..1ab6f49895ff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/Logger-test.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/Logger-test.ts @@ -10,7 +10,7 @@ import invariant from 'invariant'; import {runBabelPluginReactCompiler} from '../Babel/RunReactCompilerBabelPlugin'; import type {Logger, LoggerEvent} from '../Entrypoint'; -it('logs succesful compilation', () => { +it('logs successful compilation', () => { const logs: [string | null, LoggerEvent][] = []; const logger: Logger = { logEvent(filename, event) { diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index 727ec694bbf7..538dd4b70965 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -4062,7 +4062,7 @@ export function registerSuspenseInstanceRetry( instance.data !== SUSPENSE_PENDING_START_DATA || // The boundary is still in pending status but the document has finished loading // before we could register the event handler that would have scheduled the retry - // on load so we call teh callback now. + // on load so we call the callback now. ownerDocument.readyState !== DOCUMENT_READY_STATE_LOADING ) { callback(); diff --git a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js index e654ea88007d..73ce8d3dd29f 100644 --- a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js +++ b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js @@ -5328,7 +5328,7 @@ export function writeHoistablesForBoundary( hoistableState.stylesheets.forEach(hasStylesToHoist); // We don't actually want to flush any hoistables until the boundary is complete so we omit - // any further writing here. This is becuase unlike Resources, Hoistable Elements act more like + // any further writing here. This is because unlike Resources, Hoistable Elements act more like // regular elements, each rendered element has a unique representation in the DOM. We don't want // these elements to appear in the DOM early, before the boundary has actually completed diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index 26ba6cfe46d6..6ff107786f3b 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -9241,7 +9241,7 @@ describe('ReactDOMFizzServer', () => { it('should always flush the boundaries contributing the preamble regardless of their size', async () => { const longDescription = - `I need to make this segment somewhat large because it needs to be large enought to be outlined during the initial flush. Setting the progressive chunk size to near zero isn't enough because there is a fixed minimum size that we use to avoid doing the size tracking altogether and this needs to be larger than that at least. + `I need to make this segment somewhat large because it needs to be large enough to be outlined during the initial flush. Setting the progressive chunk size to near zero isn't enough because there is a fixed minimum size that we use to avoid doing the size tracking altogether and this needs to be larger than that at least. Unfortunately that previous paragraph wasn't quite long enough so I'll continue with some more prose and maybe throw on some repeated additional strings at the end for good measure. @@ -9277,7 +9277,7 @@ Unfortunately that previous paragraph wasn't quite long enough so I'll continue it('should track byte size of shells that may contribute to the preamble when determining if the blocking render exceeds the max size', async () => { const longDescription = - `I need to make this segment somewhat large because it needs to be large enought to be outlined during the initial flush. Setting the progressive chunk size to near zero isn't enough because there is a fixed minimum size that we use to avoid doing the size tracking altogether and this needs to be larger than that at least. + `I need to make this segment somewhat large because it needs to be large enough to be outlined during the initial flush. Setting the progressive chunk size to near zero isn't enough because there is a fixed minimum size that we use to avoid doing the size tracking altogether and this needs to be larger than that at least. Unfortunately that previous paragraph wasn't quite long enough so I'll continue with some more prose and maybe throw on some repeated additional strings at the end for good measure. diff --git a/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js b/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js index 92da0bc12306..835ffaac79dc 100644 --- a/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js @@ -1260,7 +1260,7 @@ describe('ReactDOMServerHydration', () => { } // @TODO changes made to sending Fizz errors to client led to the insertion of templates in client rendered - // suspense boundaries. This leaks in this test becuase the client rendered suspense boundary appears like + // suspense boundaries. This leaks in this test because the client rendered suspense boundary appears like // unhydrated tail nodes and this template is the first match. When we add special case handling for client // rendered suspense boundaries this test will likely change again expect(testMismatch(Mismatch)).toMatchInlineSnapshot(` diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 9b8c4a21bd8a..6fc4297e1b33 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -1258,7 +1258,7 @@ function completeWork( markUpdate(workInProgress); } } else { - // We use the updateHostComponent path becuase it produces + // We use the updateHostComponent path because it produces // the update queue we need for Hoistables. updateHostComponent( current, diff --git a/packages/react-reconciler/src/ReactFiberNewContext.js b/packages/react-reconciler/src/ReactFiberNewContext.js index 193cf74d3bec..ac9d766d6ae3 100644 --- a/packages/react-reconciler/src/ReactFiberNewContext.js +++ b/packages/react-reconciler/src/ReactFiberNewContext.js @@ -323,12 +323,23 @@ function propagateContextChanges( renderLanes, workInProgress, ); - if (!forcePropagateEntireTree) { - // During lazy propagation, we can defer propagating changes to - // the children, same as the consumer match above. - nextFiber = null; + // The primary children's fibers may not exist in the tree (they + // were discarded on initial mount if they suspended). However, the + // fallback children ARE in the committed tree and visible to the + // user. We need to continue propagating into the fallback subtree + // so that its context consumers are marked for re-render. + // + // The fiber structure is: + // SuspenseComponent + // → child: OffscreenComponent (primary, hidden) + // → sibling: FallbackFragment + // + // Skip the primary (hidden) subtree and jump to the fallback. + const primaryChildFragment = fiber.child; + if (primaryChildFragment !== null) { + nextFiber = primaryChildFragment.sibling; } else { - nextFiber = fiber.child; + nextFiber = null; } } else { // Traverse down. diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 6eed36e6d878..9a3953c1b5a2 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -3034,7 +3034,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes): RootExitStatus { function workLoopConcurrent(nonIdle: boolean) { // We yield every other "frame" when rendering Transition or Retries. Those are blocking // revealing new content. The purpose of this yield is not to avoid the overhead of yielding, - // which is very low, but rather to intentionally block any frequently occuring other main + // which is very low, but rather to intentionally block any frequently occurring other main // thread work like animations from starving our work. In other words, the purpose of this // is to reduce the framerate of animations to 30 frames per second. // For Idle work we yield every 5ms to keep animations going smooth. diff --git a/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js b/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js index 41f5a3559156..26874881dca5 100644 --- a/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js +++ b/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js @@ -1700,7 +1700,7 @@ describe('ReactAsyncActions', () => { 'regression: updates in an action passed to React.startTransition are batched ' + 'even if there were no updates before the first await', async () => { - // Regression for a bug that occured in an older, too-clever-by-half + // Regression for a bug that occurred in an older, too-clever-by-half // implementation of the isomorphic startTransition API. Now, the // isomorphic startTransition is literally the composition of every // reconciler instance's startTransition, so the behavior is less likely diff --git a/packages/react-reconciler/src/__tests__/ReactContextPropagation-test.js b/packages/react-reconciler/src/__tests__/ReactContextPropagation-test.js index 8fe57a2e50de..6a2b74813e15 100644 --- a/packages/react-reconciler/src/__tests__/ReactContextPropagation-test.js +++ b/packages/react-reconciler/src/__tests__/ReactContextPropagation-test.js @@ -1037,4 +1037,80 @@ describe('ReactLazyContextPropagation', () => { assertLog(['Result']); expect(root).toMatchRenderedOutput('Result'); }); + + // @gate enableLegacyCache + it('context change propagates to Suspense fallback (memo boundary)', async () => { + // When a context change occurs above a Suspense boundary that is currently + // showing its fallback, the fallback's context consumers should re-render + // with the updated value — even if there's a memo boundary between the + // provider and the Suspense boundary that prevents the fallback element + // references from changing. + const root = ReactNoop.createRoot(); + const Context = React.createContext('A'); + + let setContext; + function App() { + const [value, _setValue] = useState('A'); + setContext = _setValue; + return ( + + + + + ); + } + + const MemoizedWrapper = React.memo(function MemoizedWrapper() { + return ( + }> + + + ); + }); + + function FallbackConsumer() { + const value = useContext(Context); + return ; + } + + function AsyncChild() { + readText('async'); + return ; + } + + // Initial render — primary content suspends, fallback is shown + await act(() => { + root.render(); + }); + assertLog([ + 'Suspend! [async]', + 'Fallback: A', + 'A', + // pre-warming + 'Suspend! [async]', + ]); + expect(root).toMatchRenderedOutput('Fallback: AA'); + + // Update context while still suspended. The fallback consumer should + // re-render with the new value. + await act(() => { + setContext('B'); + }); + assertLog([ + // The Suspense boundary retries the primary children first + 'Suspend! [async]', + 'Fallback: B', + 'B', + // pre-warming + 'Suspend! [async]', + ]); + expect(root).toMatchRenderedOutput('Fallback: BB'); + + // Unsuspend. The primary content should render with the latest context. + await act(async () => { + await resolveText('async'); + }); + assertLog(['Content']); + expect(root).toMatchRenderedOutput('ContentB'); + }); }); diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index 989f9184637d..0b115199fc3b 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -387,7 +387,7 @@ export opaque type Request = { trackedPostpones: null | PostponedHoles, // Gets set to non-null while we want to track postponed holes. I.e. during a prerender. // onError is called when an error happens anywhere in the tree. It might recover. // The return string is used in production primarily to avoid leaking internals, secondarily to save bytes. - // Returning null/undefined will cause a defualt error message in production + // Returning null/undefined will cause a default error message in production onError: (error: mixed, errorInfo: ThrownInfo) => ?string, // onAllReady is called when all pending task is done but it may not have flushed yet. // This is a good time to start writing if you want only HTML and no intermediate steps.