From 90c6d1b218bbe07eeb757ce8777805a9b7f8e3c3 Mon Sep 17 00:00:00 2001 From: Joseph Savona <6425824+josephsavona@users.noreply.github.com> Date: Wed, 4 Feb 2026 10:09:28 -0500 Subject: [PATCH 1/7] [compiler][snap] More minimization improvements (#35689) * A few new minimization strategies, removing function params and array/object pattern elements * Ensure that we preserve the same set of errors based on not just category+reason but also description. --- compiler/packages/snap/src/minimize.ts | 136 ++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 3 deletions(-) diff --git a/compiler/packages/snap/src/minimize.ts b/compiler/packages/snap/src/minimize.ts index 0cce5ce1bdee..c734c9306d98 100644 --- a/compiler/packages/snap/src/minimize.ts +++ b/compiler/packages/snap/src/minimize.ts @@ -18,7 +18,7 @@ type CompileSuccess = {kind: 'success'}; type CompileParseError = {kind: 'parse_error'; message: string}; type CompileErrors = { kind: 'errors'; - errors: Array<{category: string; reason: string}>; + errors: Array<{category: string; reason: string; description: string | null}>; }; type CompileResult = CompileSuccess | CompileParseError | CompileErrors; @@ -70,7 +70,11 @@ function compileAndGetError( return {kind: 'success'}; } catch (e: unknown) { const error = e as Error & { - details?: Array<{category: string; reason: string}>; + details?: Array<{ + category: string; + reason: string; + description: string | null; + }>; }; // Check if this is a CompilerError with details if (error.details && error.details.length > 0) { @@ -79,6 +83,7 @@ function compileAndGetError( errors: error.details.map(detail => ({ category: detail.category, reason: detail.reason, + description: detail.description, })), }; } @@ -89,6 +94,7 @@ function compileAndGetError( { category: error.name ?? 'Error', reason: error.message, + description: null, }, ], }; @@ -108,7 +114,8 @@ function errorsMatch(a: CompileErrors, b: CompileResult): boolean { for (let i = 0; i < a.errors.length; i++) { if ( a.errors[i].category !== b.errors[i].category || - a.errors[i].reason !== b.errors[i].reason + a.errors[i].reason !== b.errors[i].reason || + a.errors[i].description !== b.errors[i].description ) { return false; } @@ -217,6 +224,45 @@ function* removeCallArguments(ast: t.File): Generator { } } +/** + * Generator that yields ASTs with function parameters removed one at a time + */ +function* removeFunctionParameters(ast: t.File): Generator { + // Collect all functions with parameters + const funcSites: Array<{funcIndex: number; paramCount: number}> = []; + let funcIndex = 0; + t.traverseFast(ast, node => { + if (t.isFunction(node) && node.params.length > 0) { + funcSites.push({funcIndex, paramCount: node.params.length}); + funcIndex++; + } + }); + + // For each function, try removing each parameter (from end to start) + for (const {funcIndex: targetFuncIdx, paramCount} of funcSites) { + for (let paramIdx = paramCount - 1; paramIdx >= 0; paramIdx--) { + const cloned = cloneAst(ast); + let idx = 0; + let modified = false; + + t.traverseFast(cloned, node => { + if (modified) return; + if (t.isFunction(node) && node.params.length > 0) { + if (idx === targetFuncIdx && paramIdx < node.params.length) { + node.params.splice(paramIdx, 1); + modified = true; + } + idx++; + } + }); + + if (modified) { + yield cloned; + } + } + } +} + /** * Generator that simplifies call expressions by replacing them with their arguments. * For single argument: foo(x) -> x @@ -1566,6 +1612,84 @@ function* removeObjectProperties(ast: t.File): Generator { } } +/** + * Generator that removes elements from array destructuring patterns one at a time + */ +function* removeArrayPatternElements(ast: t.File): Generator { + // Collect all array patterns with elements + const patternSites: Array<{patternIndex: number; elementCount: number}> = []; + let patternIndex = 0; + t.traverseFast(ast, node => { + if (t.isArrayPattern(node) && node.elements.length > 0) { + patternSites.push({patternIndex, elementCount: node.elements.length}); + patternIndex++; + } + }); + + // For each pattern, try removing each element (from end to start) + for (const {patternIndex: targetPatternIdx, elementCount} of patternSites) { + for (let elemIdx = elementCount - 1; elemIdx >= 0; elemIdx--) { + const cloned = cloneAst(ast); + let idx = 0; + let modified = false; + + t.traverseFast(cloned, node => { + if (modified) return; + if (t.isArrayPattern(node) && node.elements.length > 0) { + if (idx === targetPatternIdx && elemIdx < node.elements.length) { + node.elements.splice(elemIdx, 1); + modified = true; + } + idx++; + } + }); + + if (modified) { + yield cloned; + } + } + } +} + +/** + * Generator that removes properties from object destructuring patterns one at a time + */ +function* removeObjectPatternProperties(ast: t.File): Generator { + // Collect all object patterns with properties + const patternSites: Array<{patternIndex: number; propCount: number}> = []; + let patternIndex = 0; + t.traverseFast(ast, node => { + if (t.isObjectPattern(node) && node.properties.length > 0) { + patternSites.push({patternIndex, propCount: node.properties.length}); + patternIndex++; + } + }); + + // For each pattern, try removing each property (from end to start) + for (const {patternIndex: targetPatternIdx, propCount} of patternSites) { + for (let propIdx = propCount - 1; propIdx >= 0; propIdx--) { + const cloned = cloneAst(ast); + let idx = 0; + let modified = false; + + t.traverseFast(cloned, node => { + if (modified) return; + if (t.isObjectPattern(node) && node.properties.length > 0) { + if (idx === targetPatternIdx && propIdx < node.properties.length) { + node.properties.splice(propIdx, 1); + modified = true; + } + idx++; + } + }); + + if (modified) { + yield cloned; + } + } + } +} + /** * Generator that simplifies assignment expressions (a = b) -> a or b */ @@ -1852,8 +1976,14 @@ function* simplifyIdentifiersRenameRef(ast: t.File): Generator { const simplificationStrategies = [ {name: 'removeStatements', generator: removeStatements}, {name: 'removeCallArguments', generator: removeCallArguments}, + {name: 'removeFunctionParameters', generator: removeFunctionParameters}, {name: 'removeArrayElements', generator: removeArrayElements}, {name: 'removeObjectProperties', generator: removeObjectProperties}, + {name: 'removeArrayPatternElements', generator: removeArrayPatternElements}, + { + name: 'removeObjectPatternProperties', + generator: removeObjectPatternProperties, + }, {name: 'removeJSXAttributes', generator: removeJSXAttributes}, {name: 'removeJSXChildren', generator: removeJSXChildren}, {name: 'removeJSXFragmentChildren', generator: removeJSXFragmentChildren}, From 22a20e1f2f557b99115d82b639ff5a32b6453cb6 Mon Sep 17 00:00:00 2001 From: Anton Chesnokov Date: Wed, 4 Feb 2026 20:07:17 +0400 Subject: [PATCH 2/7] [compiler] Fix setState-in-effect for React.useEffect namespace calls (#35377) (#35419) ## Summary Fix react-hooks/set-state-in-effect false negatives when Hooks are called via a namespace import (e.g. `import * as React from 'react'` and `React.useEffect(...))`. The validation now checks the MethodCall property (the actual hook function) instead of the receiver object. Issue: Bug: #35377 ## How did you test this change? Added a regression fixture; Ran tests and verified it reports `EffectSetState` and matches the expected output. Screenshot 2025-12-27 at 14 13 38 --- .../Validation/ValidateNoSetStateInEffects.ts | 2 +- ...-setState-in-useEffect-namespace.expect.md | 42 +++++++++++++++++++ ...invalid-setState-in-useEffect-namespace.js | 10 +++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-namespace.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-namespace.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts index eba2fdf5bbdd..2457e0d7b99e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts @@ -97,7 +97,7 @@ export function validateNoSetStateInEffects( case 'CallExpression': { const callee = instr.value.kind === 'MethodCall' - ? instr.value.receiver + ? instr.value.property : instr.value.callee; if (isUseEffectEventType(callee.identifier)) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-namespace.expect.md new file mode 100644 index 000000000000..b7f823e46d4a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-namespace.expect.md @@ -0,0 +1,42 @@ + +## Input + +```javascript +// @loggerTestOnly @validateNoSetStateInEffects @outputMode:"lint" +import * as React from 'react'; + +function Component() { + const [state, setState] = React.useState(0); + React.useEffect(() => { + setState(s => s + 1); + }); + return state; +} + +``` + +## Code + +```javascript +// @loggerTestOnly @validateNoSetStateInEffects @outputMode:"lint" +import * as React from "react"; + +function Component() { + const [state, setState] = React.useState(0); + React.useEffect(() => { + setState((s) => s + 1); + }); + return state; +} + +``` + +## Logs + +``` +{"kind":"CompileError","detail":{"options":{"category":"EffectSetState","reason":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":200},"end":{"line":7,"column":12,"index":208},"filename":"invalid-setState-in-useEffect-namespace.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":100},"end":{"line":10,"column":1,"index":245},"filename":"invalid-setState-in-useEffect-namespace.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":1,"prunedMemoValues":1} +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-namespace.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-namespace.js new file mode 100644 index 000000000000..0748c1206f59 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-namespace.js @@ -0,0 +1,10 @@ +// @loggerTestOnly @validateNoSetStateInEffects @outputMode:"lint" +import * as React from 'react'; + +function Component() { + const [state, setState] = React.useState(0); + React.useEffect(() => { + setState(s => s + 1); + }); + return state; +} From c137dd6f5484f1bb68a64f07dd0e40d474a29759 Mon Sep 17 00:00:00 2001 From: Jorge Cabiedes <57368278+jorge-cab@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:24:24 -0800 Subject: [PATCH 3/7] Fix exhaustive deps bug with flow type casting. (#35691) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: I noticed there's a bug where the lint will recognize the type on a cast annotation as a missing dependency; ``` function MyComponent() { type ColumnKey = 'id' | 'name'; type Item = {id: string, name: string}; const columns = useMemo( () => [ { type: 'text', key: 'id', } as TextColumn, ^^^^^^^^ here ], [], ); } ``` This is due to the AST of AsExpressions being something like: AsExpression └── typeAnnotation: GenericTypeAnnotation └── typeParameters: TypeParameterInstantiation └── params[0]: GenericTypeAnnotation └── id: Identifier (name: "ColumnKey") Where `ColumnKey` never has a TypeParameter Annotation. So we need to consider it to be a flow type due to it belonging to a GenericTypeAnnotation Test Plan: Added unit tests Before: ``` Test Suites: 1 failed, 2 passed, 3 total Tests: 2 failed, 5065 passed, 5067 total Snapshots: 0 total Time: 16.517 s Ran all test suites. error Command failed with exit code 1. ``` After: ``` PASS __tests__/ReactCompilerRuleTypescript-test.ts PASS __tests__/ESLintRulesOfHooks-test.js (6.192 s) PASS __tests__/ESLintRuleExhaustiveDeps-test.js (9.97 s) Test Suites: 3 passed, 3 total Tests: 5067 passed, 5067 total Snapshots: 0 total Time: 10.21 s, estimated 11 s Ran all test suites. ✨ Done in 12.66s. ``` --- .../ESLintRuleExhaustiveDeps-test.js | 19 +++++++++++++++++++ .../src/rules/ExhaustiveDeps.ts | 11 +++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js index b479ce48521c..29e956d314a2 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js @@ -7913,6 +7913,25 @@ const testsFlow = { } `, }, + // Flow type aliases in type assertions should not be flagged as missing dependencies + { + code: normalizeIndent` + function MyComponent() { + type ColumnKey = 'id' | 'name'; + type Item = {id: string, name: string}; + + const columns = useMemo( + () => [ + { + type: 'text', + key: 'id', + } as TextColumn, + ], + [], + ); + } + `, + }, ], invalid: [ { diff --git a/packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts b/packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts index 05321ffb46f6..6b790680608d 100644 --- a/packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts +++ b/packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts @@ -21,7 +21,7 @@ import type { VariableDeclarator, } from 'estree'; -import { getAdditionalEffectHooksFromSettings } from '../shared/Utils'; +import {getAdditionalEffectHooksFromSettings} from '../shared/Utils'; type DeclaredDependency = { key: string; @@ -80,7 +80,6 @@ const rule = { const rawOptions = context.options && context.options[0]; const settings = context.settings || {}; - // Parse the `additionalHooks` regex. // Use rule-level additionalHooks if provided, otherwise fall back to settings const additionalHooks = @@ -565,8 +564,12 @@ const rule = { continue; } // Ignore Flow type parameters - // @ts-expect-error We don't have flow types - if (def.type === 'TypeParameter') { + if ( + // @ts-expect-error We don't have flow types + def.type === 'TypeParameter' || + // @ts-expect-error Flow-specific AST node type + dependencyNode.parent?.type === 'GenericTypeAnnotation' + ) { continue; } From cf993fb457417e0f20535b1fd42c3f45df966583 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Wed, 4 Feb 2026 19:43:23 +0100 Subject: [PATCH 4/7] [Flight] Fix stack overflow in `visitAsyncNode` with deep async chains (#35612) Database libraries like Gel/EdgeDB can create very long linear chains of async sequences through temporal async sequencing in connection pools. The recursive traversal of `node.previous` chains in `visitAsyncNode` causes stack overflow on these deep chains. The fix converts the `previous` chain traversal from recursive to iterative. We collect the chain into an array, then process from deepest to shallowest. The `awaited` traversal remains recursive since its depth is bounded by promise dependency depth, not by the number of event loop turns. Each `awaited` branch still benefits from the iterative `previous` handling within its own traversal. I've verified that this fixes the [repro](https://github.com/jere-co/next-debug) provided in #35246. closes #35246 --- .../react-server/src/ReactFlightServer.js | 76 +- .../ReactFlightAsyncDebugInfo-test.js | 807 +++++++++++------- 2 files changed, 566 insertions(+), 317 deletions(-) diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 6edfbaa4df3e..80d5f3e5c005 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -2345,19 +2345,53 @@ function visitAsyncNode( >, cutOff: number, ): void | null | PromiseNode | IONode { - if (visited.has(node)) { - // It's possible to visit them same node twice when it's part of both an "awaited" path - // and a "previous" path. This also gracefully handles cycles which would be a bug. - return visited.get(node); - } - // Set it as visited early in case we see ourselves before returning. - visited.set(node, null); - const result = visitAsyncNodeImpl(request, task, node, visited, cutOff); - if (result !== null) { - // If we ended up with a value, let's use that value for future visits. - visited.set(node, result); + // Collect the previous chain iteratively instead of recursively to avoid + // stack overflow on deep chains. We process from deepest to shallowest so + // each node has its previousIONode available. + const chain: Array = []; + let current: AsyncSequence | null = node; + + while (current !== null) { + if (visited.has(current)) { + break; + } + chain.push(current); + current = current.previous; } - return result; + + let previousIONode: void | null | PromiseNode | IONode = + current !== null ? visited.get(current) : null; + + // Process from deepest to shallowest (reverse order). + for (let i = chain.length - 1; i >= 0; i--) { + const n = chain[i]; + // Set it as visited early in case we see the node again before returning. + visited.set(n, null); + + const result = visitAsyncNodeImpl( + request, + task, + n, + visited, + cutOff, + previousIONode, + ); + + if (result !== null) { + // If we ended up with a value, let's use that value for future visits. + visited.set(n, result); + } + + if (result === undefined) { + // Undefined is used as a signal that we found a suitable aborted node + // and we don't have to find further aborted nodes. + return undefined; + } + + previousIONode = result; + } + + return previousIONode; } function visitAsyncNodeImpl( @@ -2369,6 +2403,7 @@ function visitAsyncNodeImpl( void | null | PromiseNode | IONode, >, cutOff: number, + previousIONode: void | null | PromiseNode | IONode, ): void | null | PromiseNode | IONode { if (node.end >= 0 && node.end <= request.timeOrigin) { // This was already resolved when we started this render. It must have been either something @@ -2377,23 +2412,6 @@ function visitAsyncNodeImpl( return null; } - let previousIONode: void | null | PromiseNode | IONode = null; - // First visit anything that blocked this sequence to start in the first place. - if (node.previous !== null) { - previousIONode = visitAsyncNode( - request, - task, - node.previous, - visited, - cutOff, - ); - if (previousIONode === undefined) { - // Undefined is used as a signal that we found a suitable aborted node and we don't have to find - // further aborted nodes. - return undefined; - } - } - // `found` represents the return value of the following switch statement. // We can't use multiple `return` statements in the switch statement // since that prevents Closure compiler from inlining `visitAsyncImpl` diff --git a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js index 2570c0d12e2a..336d797efb61 100644 --- a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js +++ b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js @@ -5,6 +5,7 @@ import {patchSetImmediate} from '../../../../scripts/jest/patchSetImmediate'; +import crypto from 'crypto'; import fs from 'fs/promises'; import path from 'path'; @@ -160,9 +161,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 129, + 130, + 109, 109, - 108, 50, ], ], @@ -184,9 +185,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 129, + 130, + 109, 109, - 108, 50, ], ], @@ -195,25 +196,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 86, + 87, 12, - 85, + 86, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 110, + 111, 13, - 109, + 110, 5, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 118, + 119, 26, - 117, + 118, 5, ], ], @@ -232,9 +233,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 129, + 130, + 109, 109, - 108, 50, ], ], @@ -243,17 +244,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 110, + 111, 13, - 109, + 110, 5, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 118, + 119, 26, - 117, + 118, 5, ], ], @@ -278,9 +279,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 129, + 130, + 109, 109, - 108, 50, ], ], @@ -289,25 +290,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 86, + 87, 12, - 85, + 86, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 111, + 112, 21, - 109, + 110, 5, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 118, + 119, 20, - 117, + 118, 5, ], ], @@ -326,9 +327,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 129, + 130, + 109, 109, - 108, 50, ], ], @@ -337,17 +338,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 113, + 114, 21, - 109, + 110, 5, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 118, + 119, 20, - 117, + 118, 5, ], ], @@ -367,9 +368,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 120, + 121, 60, - 117, + 118, 5, ], ], @@ -391,9 +392,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 129, + 130, + 109, 109, - 108, 50, ], ], @@ -402,17 +403,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 86, + 87, 12, - 85, + 86, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 111, + 112, 21, - 109, + 110, 5, ], ], @@ -431,9 +432,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 120, + 121, 60, - 117, + 118, 5, ], ], @@ -442,9 +443,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "InnerComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 126, + 127, 35, - 123, + 124, 5, ], ], @@ -625,9 +626,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 589, + 590, 40, - 570, + 571, 49, ], [ @@ -657,9 +658,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 589, + 590, 40, - 570, + 571, 49, ], [ @@ -676,25 +677,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 86, + 87, 12, - 85, + 86, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 572, + 573, 13, - 571, + 572, 5, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 577, + 578, 36, - 576, + 577, 5, ], ], @@ -713,9 +714,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 589, + 590, 40, - 570, + 571, 49, ], [ @@ -732,17 +733,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 572, + 573, 13, - 571, + 572, 5, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 577, + 578, 36, - 576, + 577, 5, ], ], @@ -762,9 +763,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 579, + 580, 60, - 576, + 577, 5, ], ], @@ -783,9 +784,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 589, + 590, 40, - 570, + 571, 49, ], [ @@ -802,25 +803,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 86, + 87, 12, - 85, + 86, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 572, + 573, 13, - 571, + 572, 5, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 578, + 579, 22, - 576, + 577, 5, ], ], @@ -839,9 +840,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 579, + 580, 60, - 576, + 577, 5, ], ], @@ -850,9 +851,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "InnerComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 585, + 586, 40, - 582, + 583, 5, ], ], @@ -927,9 +928,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 896, + 897, 109, - 883, + 884, 80, ], ], @@ -948,9 +949,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 896, + 897, 109, - 883, + 884, 80, ], ], @@ -967,9 +968,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 896, + 897, 109, - 883, + 884, 80, ], ], @@ -1041,9 +1042,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1010, + 1011, 109, - 1001, + 1002, 94, ], ], @@ -1114,9 +1115,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1083, + 1084, 109, - 1059, + 1060, 50, ], ], @@ -1198,9 +1199,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1167, + 1168, 109, - 1150, + 1151, 63, ], ], @@ -1217,17 +1218,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "fetchThirdParty", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 93, + 94, 40, - 91, + 92, 3, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1163, + 1164, 24, - 1162, + 1163, 5, ], ], @@ -1249,17 +1250,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "fetchThirdParty", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 93, + 94, 40, - 91, + 92, 3, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1163, + 1164, 24, - 1162, + 1163, 5, ], ], @@ -1268,25 +1269,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 86, + 87, 12, - 85, + 86, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1152, + 1153, 13, - 1151, + 1152, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1158, + 1159, 24, - 1157, + 1158, 5, ], ], @@ -1305,17 +1306,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "fetchThirdParty", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 93, + 94, 40, - 91, + 92, 3, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1163, + 1164, 24, - 1162, + 1163, 5, ], ], @@ -1324,17 +1325,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1152, + 1153, 13, - 1151, + 1152, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1158, + 1159, 24, - 1157, + 1158, 5, ], ], @@ -1359,17 +1360,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "fetchThirdParty", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 93, + 94, 40, - 91, + 92, 3, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1163, + 1164, 24, - 1162, + 1163, 5, ], ], @@ -1378,25 +1379,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 86, + 87, 12, - 85, + 86, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1153, + 1154, 13, - 1151, + 1152, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1158, + 1159, 18, - 1157, + 1158, 5, ], ], @@ -1415,17 +1416,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "fetchThirdParty", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 93, + 94, 40, - 91, + 92, 3, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1163, + 1164, 24, - 1162, + 1163, 5, ], ], @@ -1434,17 +1435,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1153, + 1154, 13, - 1151, + 1152, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1158, + 1159, 18, - 1157, + 1158, 5, ], ], @@ -1544,9 +1545,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1508, + 1509, 40, - 1491, + 1492, 62, ], [ @@ -1576,9 +1577,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1508, + 1509, 40, - 1491, + 1492, 62, ], [ @@ -1595,25 +1596,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 86, + 87, 12, - 85, + 86, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1493, + 1494, 13, - 1492, + 1493, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1503, + 1504, 13, - 1502, + 1503, 5, ], ], @@ -1632,9 +1633,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1508, + 1509, 40, - 1491, + 1492, 62, ], [ @@ -1651,17 +1652,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1493, + 1494, 13, - 1492, + 1493, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1503, + 1504, 13, - 1502, + 1503, 5, ], ], @@ -1681,9 +1682,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1504, + 1505, 60, - 1502, + 1503, 5, ], ], @@ -1705,9 +1706,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1508, + 1509, 40, - 1491, + 1492, 62, ], [ @@ -1724,25 +1725,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 86, + 87, 12, - 85, + 86, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1493, + 1494, 13, - 1492, + 1493, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1503, + 1504, 13, - 1502, + 1503, 5, ], ], @@ -1761,9 +1762,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1504, + 1505, 60, - 1502, + 1503, 5, ], ], @@ -1772,9 +1773,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Child", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1498, + 1499, 28, - 1497, + 1498, 5, ], ], @@ -1857,9 +1858,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1821, + 1822, 40, - 1805, + 1806, 57, ], [ @@ -1889,9 +1890,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1821, + 1822, 40, - 1805, + 1806, 57, ], [ @@ -1908,25 +1909,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 86, + 87, 12, - 85, + 86, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1807, + 1808, 13, - 1806, + 1807, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1816, + 1817, 23, - 1815, + 1816, 5, ], ], @@ -1945,9 +1946,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1821, + 1822, 40, - 1805, + 1806, 57, ], [ @@ -1964,17 +1965,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1807, + 1808, 13, - 1806, + 1807, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1816, + 1817, 23, - 1815, + 1816, 5, ], ], @@ -1994,9 +1995,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1817, + 1818, 60, - 1815, + 1816, 5, ], ], @@ -2015,9 +2016,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1821, + 1822, 40, - 1805, + 1806, 57, ], [ @@ -2034,25 +2035,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 86, + 87, 12, - 85, + 86, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1807, + 1808, 13, - 1806, + 1807, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1816, + 1817, 23, - 1815, + 1816, 5, ], ], @@ -2066,9 +2067,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1817, + 1818, 60, - 1815, + 1816, 5, ], ], @@ -2153,9 +2154,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2117, + 2118, 40, - 2099, + 2100, 80, ], [ @@ -2185,9 +2186,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2117, + 2118, 40, - 2099, + 2100, 80, ], [ @@ -2204,25 +2205,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 86, + 87, 12, - 85, + 86, 3, ], [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2107, + 2108, 13, - 2105, + 2106, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2112, + 2113, 13, - 2111, + 2112, 5, ], ], @@ -2241,9 +2242,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2117, + 2118, 40, - 2099, + 2100, 80, ], [ @@ -2260,17 +2261,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2107, + 2108, 13, - 2105, + 2106, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2112, + 2113, 13, - 2111, + 2112, 5, ], ], @@ -2292,9 +2293,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2117, + 2118, 40, - 2099, + 2100, 80, ], [ @@ -2311,33 +2312,33 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 86, + 87, 12, - 85, + 86, 3, ], [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2101, + 2102, 13, - 2100, + 2101, 5, ], [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2106, + 2107, 15, - 2105, + 2106, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2112, + 2113, 13, - 2111, + 2112, 5, ], ], @@ -2356,9 +2357,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2117, + 2118, 40, - 2099, + 2100, 80, ], [ @@ -2375,25 +2376,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2101, + 2102, 13, - 2100, + 2101, 5, ], [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2106, + 2107, 15, - 2105, + 2106, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2112, + 2113, 13, - 2111, + 2112, 5, ], ], @@ -2415,9 +2416,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2117, + 2118, 40, - 2099, + 2100, 80, ], [ @@ -2434,17 +2435,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 86, + 87, 12, - 85, + 86, 3, ], [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2102, + 2103, 13, - 2100, + 2101, 5, ], ], @@ -2463,9 +2464,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2117, + 2118, 40, - 2099, + 2100, 80, ], [ @@ -2482,9 +2483,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2102, + 2103, 13, - 2100, + 2101, 5, ], ], @@ -2557,9 +2558,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2526, + 2527, 109, - 2515, + 2516, 58, ], ], @@ -2581,9 +2582,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2526, + 2527, 109, - 2515, + 2516, 58, ], ], @@ -2592,25 +2593,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 86, + 87, 12, - 85, + 86, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2517, + 2518, 14, - 2516, + 2517, 5, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2523, + 2524, 20, - 2522, + 2523, 5, ], ], @@ -2629,9 +2630,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2526, + 2527, 109, - 2515, + 2516, 58, ], ], @@ -2640,17 +2641,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2517, + 2518, 23, - 2516, + 2517, 5, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2523, + 2524, 20, - 2522, + 2523, 5, ], ], @@ -2729,9 +2730,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2693, + 2694, 40, - 2681, + 2682, 56, ], [ @@ -2761,9 +2762,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2693, + 2694, 40, - 2681, + 2682, 56, ], [ @@ -2780,17 +2781,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 86, + 87, 12, - 85, + 86, 3, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2689, + 2690, 20, - 2688, + 2689, 5, ], ], @@ -2809,9 +2810,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2693, + 2694, 40, - 2681, + 2682, 56, ], [ @@ -2828,9 +2829,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2689, + 2690, 20, - 2688, + 2689, 5, ], ], @@ -2923,9 +2924,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2882, + 2883, 40, - 2861, + 2862, 42, ], [ @@ -2955,9 +2956,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2882, + 2883, 40, - 2861, + 2862, 42, ], [ @@ -2974,17 +2975,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2868, + 2869, 15, - 2867, + 2868, 15, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2877, + 2878, 19, - 2876, + 2877, 5, ], ], @@ -3003,9 +3004,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2882, + 2883, 40, - 2861, + 2862, 42, ], [ @@ -3022,17 +3023,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2868, + 2869, 15, - 2867, + 2868, 15, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2877, + 2878, 19, - 2876, + 2877, 5, ], ], @@ -3054,9 +3055,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2882, + 2883, 40, - 2861, + 2862, 42, ], [ @@ -3073,9 +3074,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2877, + 2878, 25, - 2876, + 2877, 5, ], ], @@ -3094,9 +3095,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2882, + 2883, 40, - 2861, + 2862, 42, ], [ @@ -3113,9 +3114,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 2877, + 2878, 25, - 2876, + 2877, 5, ], ], @@ -3191,9 +3192,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 3158, + 3159, 19, - 3146, + 3147, 36, ], ], @@ -3215,9 +3216,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 3158, + 3159, 19, - 3146, + 3147, 36, ], ], @@ -3226,9 +3227,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 3150, + 3151, 7, - 3148, + 3149, 5, ], ], @@ -3247,9 +3248,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 3158, + 3159, 19, - 3146, + 3147, 36, ], ], @@ -3258,9 +3259,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 3152, + 3153, 7, - 3148, + 3149, 5, ], ], @@ -3385,9 +3386,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 3303, + 3304, 20, - 3302, + 3303, 5, ], ], @@ -3402,4 +3403,234 @@ describe('ReactFlightAsyncDebugInfo', () => { `); } }); + + // Regression test: Database clients like Gel/EdgeDB with connection pools can + // create very long chains of async nodes linked via `previous` pointers. + // visitAsyncNode must handle chains of thousands of nodes without stack + // overflow. + // + // The pattern that creates deep chains (Event/messageWaiter): + // 1. Create a Promise resolved by an I/O callback + // 2. Start I/O, await the Promise + // 3. When I/O resolves, code continues in the callback context + // 4. The next iteration's async nodes link via `previous` to the current + // context + it('handles deep linear async chains from connection pool patterns', async () => { + // Replicate Gel's Event class pattern, a Promise resolved externally by I/O + class Event { + constructor() { + this._resolve = null; + this._promise = new Promise(resolve => { + this._resolve = resolve; + }); + } + + wait() { + return this._promise; + } + + set() { + this._resolve(true); + } + } + + // Replicate Gel's _waitForMessage pattern: + // Each iteration creates an Event, schedules I/O to resolve it, and awaits. + // The next iteration runs in the context of the previous I/O callback, + // creating linear chains of previous pointers. + async function buildLinearChain(depth) { + for (let i = 0; i < depth; i++) { + const event = new Event(); + // crypto.randomBytes uses the thread pool and is recognized as I/O + // (type='RANDOMBYTESREQUEST'). It's much faster than setTimeout. + crypto.randomBytes(1, () => event.set()); + await event.wait(); + // After this await resolves, we're in the crypto callback's context + // The next Event will be created in this context, linking prev pointers + } + } + + async function Component() { + // Use 2000 iterations to ensure regression would be caught. Using + // crypto.randomBytes keeps the test fast. + await buildLinearChain(2000); + return 'done'; + } + + const stream = ReactServerDOMServer.renderToPipeableStream( + , + {}, + { + filterStackFrame: (filename, functionName) => { + // Custom filter that treats Event and buildLinearChain as library + // code. This simulates how real DB libraries like Gel/EdgeDB would be + // filtered. + if ( + functionName === 'new Event' || + functionName === 'buildLinearChain' || + functionName === '_loop' // Generated name for the for-loop + ) { + return false; + } + return filterStackFrame(filename, functionName); + }, + }, + ); + + const readable = new Stream.PassThrough(streamOptions); + + const result = ReactServerDOMClient.createFromNodeStream(readable, { + moduleMap: {}, + moduleLoading: {}, + }); + stream.pipe(readable); + + // This should not throw "Maximum call stack size exceeded" + expect(await result).toBe('done'); + + await finishLoadingStream(readable); + if ( + __DEV__ && + gate( + flags => + flags.enableComponentPerformanceTrack && flags.enableAsyncDebugInfo, + ) + ) { + // With library code filtered out, we should only see the Component's + // debug info, not thousands of entries from the internal + // Event/buildLinearChain operations. + expect(getDebugInfo(result)).toMatchInlineSnapshot(` + [ + { + "time": 0, + }, + { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 3461, + 40, + 3418, + 72, + ], + [ + "new Promise", + "", + 0, + 0, + 0, + 0, + ], + ], + }, + { + "time": 0, + }, + { + "awaited": { + "end": 0, + "env": "Server", + "name": "buildLinearChain", + "owner": { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 3461, + 40, + 3418, + 72, + ], + [ + "new Promise", + "", + 0, + 0, + 0, + 0, + ], + ], + }, + "stack": [ + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 3456, + 13, + 3453, + 5, + ], + ], + "start": 0, + "value": { + "value": undefined, + }, + }, + "env": "Server", + "owner": { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 3461, + 40, + 3418, + 72, + ], + [ + "new Promise", + "", + 0, + 0, + 0, + 0, + ], + ], + }, + "stack": [ + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 3456, + 13, + 3453, + 5, + ], + ], + }, + { + "time": 0, + }, + { + "time": 0, + }, + { + "awaited": { + "byteSize": 0, + "end": 0, + "name": "rsc stream", + "owner": null, + "start": 0, + "value": { + "value": "stream", + }, + }, + }, + ] + `); + } + }); }); From 6913ea4d28229d066603bff9fc1170334e151a4a Mon Sep 17 00:00:00 2001 From: Ricky Date: Wed, 4 Feb 2026 13:58:34 -0500 Subject: [PATCH 5/7] [flags] Add `enableParallelTransitions` (#35392) ## Overview Adds a feature flag `enableParallelTransitions` to experiment with engantling transitions less often. ## Motivation Currently we over-entangle transition lanes. It's a common misunderstanding that React entangles all transitions, always. We actually will complete transitions independently in many cases. For example, [this codepen](https://codepen.io/GabbeV/pen/pvyKBrM) from [@gabbev](https://bsky.app/profile/gabbev.bsky.social/post/3m6uq2abihk2x) shows transitions completing independently. However, in many cases we entangle when we don't need to, instead of letting the independent transitons complete independently. We still want to entangle for updates that happen on the same queue. ## Example As an example of what this flag would change, consider two independent counter components: ```js function Counter({ label }) { const [count, setCount] = useState(0); return (
{use(readCache(`${label} ${count}`))}
); } ``` ```js export default function App() { return ( <> ); } ``` ### Before The behavior today is to entange them, meaning they always commit together: https://github.com/user-attachments/assets/adead60e-8a98-4a20-a440-1efdf85b2142 ### After In this experiment, they will complete independently (if they don't depend on each other): https://github.com/user-attachments/assets/181632b5-3c92-4a29-a571-3637f3fab8cd ## Early Research This change is in early research, and is not in the experimental channel. We're going to experiment with this at Meta to understand how much of a breaking change, and how beneficial it is before commiting to shipping it in experimental and beyond. --- .../react-reconciler/src/ReactFiberLane.js | 4 + .../src/ReactFiberWorkLoop.js | 6 + .../src/__tests__/ReactDeferredValue-test.js | 193 ++++++++++- .../src/__tests__/ReactTransition-test.js | 313 ++++++++++++++++++ packages/shared/ReactFeatureFlags.js | 3 + .../forks/ReactFeatureFlags.native-fb.js | 1 + .../forks/ReactFeatureFlags.native-oss.js | 1 + .../forks/ReactFeatureFlags.test-renderer.js | 1 + ...actFeatureFlags.test-renderer.native-fb.js | 1 + .../ReactFeatureFlags.test-renderer.www.js | 1 + .../forks/ReactFeatureFlags.www-dynamic.js | 1 + .../shared/forks/ReactFeatureFlags.www.js | 1 + 12 files changed, 517 insertions(+), 9 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberLane.js b/packages/react-reconciler/src/ReactFiberLane.js index b54047cb4aa7..987f0338ad1a 100644 --- a/packages/react-reconciler/src/ReactFiberLane.js +++ b/packages/react-reconciler/src/ReactFiberLane.js @@ -29,6 +29,7 @@ import { disableLegacyMode, enableDefaultTransitionIndicator, enableGestureTransition, + enableParallelTransitions, } from 'shared/ReactFeatureFlags'; import {isDevToolsPresent} from './ReactFiberDevToolsHook'; import {clz32} from './clz32'; @@ -208,6 +209,9 @@ function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes { case TransitionLane8: case TransitionLane9: case TransitionLane10: + if (enableParallelTransitions) { + return getHighestPriorityLane(lanes); + } return lanes & TransitionUpdateLanes; case TransitionLane11: case TransitionLane12: diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index b03f5eff159c..d055b271ad77 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -58,6 +58,7 @@ import { enableViewTransition, enableGestureTransition, enableDefaultTransitionIndicator, + enableParallelTransitions, } from 'shared/ReactFeatureFlags'; import {resetOwnerStackLimit} from 'shared/ReactOwnerStackReset'; import ReactSharedInternals from 'shared/ReactSharedInternals'; @@ -1777,6 +1778,11 @@ function markRootSuspended( spawnedLane: Lane, didAttemptEntireTree: boolean, ) { + if (enableParallelTransitions) { + // When suspending, we should always mark the entangled lanes as suspended. + suspendedLanes = getEntangledLanes(root, suspendedLanes); + } + // When suspending, we should always exclude lanes that were pinged or (more // rarely, since we try to avoid it) updated during the render phase. suspendedLanes = removeLanes(suspendedLanes, workInProgressRootPingedLanes); diff --git a/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js b/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js index e4fe796fd353..3a348307f4cd 100644 --- a/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js +++ b/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js @@ -420,9 +420,13 @@ describe('ReactDeferredValue', () => { // The initial value suspended, so we attempt the final value, which // also suspends. 'Suspend! [Final]', - // pre-warming - 'Suspend! [Loading...]', - 'Suspend! [Final]', + ...(gate('enableParallelTransitions') + ? [] + : [ + // Existing bug: Unnecessary pre-warm. + 'Suspend! [Loading...]', + 'Suspend! [Final]', + ]), ]); expect(root).toMatchRenderedOutput(null); @@ -439,6 +443,171 @@ describe('ReactDeferredValue', () => { }, ); + it( + 'if a suspended render spawns a deferred task that suspends on a sibling, ' + + 'we can finish the original task if the original sibling loads first', + async () => { + function App() { + const deferredText = useDeferredValue(`Final`, `Loading...`); + return ( + <> + {' '} + + + ); + } + + const root = ReactNoop.createRoot(); + await act(() => root.render()); + assertLog([ + 'Suspend! [Loading...]', + // The initial value suspended, so we attempt the final value, which + // also suspends. + 'Suspend! [Final]', + 'Suspend! [Sibling: Final]', + ...(gate('enableParallelTransitions') + ? [ + // With parallel transitions, + // we do not continue pre-warming. + ] + : [ + 'Suspend! [Loading...]', + 'Suspend! [Sibling: Loading...]', + 'Suspend! [Final]', + 'Suspend! [Sibling: Final]', + ]), + ]); + expect(root).toMatchRenderedOutput(null); + + // The final value loads, so we can skip the initial value entirely. + await act(() => { + resolveText('Final'); + }); + assertLog(['Final', 'Suspend! [Sibling: Final]']); + expect(root).toMatchRenderedOutput(null); + + // The initial value resolves first, so we render that. + await act(() => resolveText('Loading...')); + assertLog([ + 'Loading...', + 'Suspend! [Sibling: Loading...]', + 'Final', + 'Suspend! [Sibling: Final]', + ...(gate('enableParallelTransitions') + ? [ + // With parallel transitions, + // we do not continue pre-warming. + ] + : [ + 'Loading...', + 'Suspend! [Sibling: Loading...]', + 'Final', + 'Suspend! [Sibling: Final]', + ]), + ]); + expect(root).toMatchRenderedOutput(null); + + // The Final sibling loads, we're unblocked and commit. + await act(() => { + resolveText('Sibling: Final'); + }); + assertLog(['Final', 'Sibling: Final']); + expect(root).toMatchRenderedOutput('Final Sibling: Final'); + + // We already rendered the Final value, so nothing happens + await act(() => { + resolveText('Sibling: Loading...'); + }); + assertLog([]); + expect(root).toMatchRenderedOutput('Final Sibling: Final'); + }, + ); + + it( + 'if a suspended render spawns a deferred task that suspends on a sibling,' + + ' we can switch to the deferred task without finishing the original one', + async () => { + function App() { + const deferredText = useDeferredValue(`Final`, `Loading...`); + return ( + <> + {' '} + + + ); + } + + const root = ReactNoop.createRoot(); + await act(() => root.render()); + assertLog([ + 'Suspend! [Loading...]', + // The initial value suspended, so we attempt the final value, which + // also suspends. + 'Suspend! [Final]', + 'Suspend! [Sibling: Final]', + ...(gate('enableParallelTransitions') + ? [ + // With parallel transitions, + // we do not continue pre-warming. + ] + : [ + 'Suspend! [Loading...]', + 'Suspend! [Sibling: Loading...]', + 'Suspend! [Final]', + 'Suspend! [Sibling: Final]', + ]), + ]); + expect(root).toMatchRenderedOutput(null); + + // The final value loads, so we can skip the initial value entirely. + await act(() => { + resolveText('Final'); + }); + assertLog(['Final', 'Suspend! [Sibling: Final]']); + expect(root).toMatchRenderedOutput(null); + + // The initial value resolves first, so we render that. + await act(() => resolveText('Loading...')); + assertLog([ + 'Loading...', + 'Suspend! [Sibling: Loading...]', + 'Final', + 'Suspend! [Sibling: Final]', + ...(gate('enableParallelTransitions') + ? [ + // With parallel transitions, + // we do not continue pre-warming. + ] + : [ + 'Loading...', + 'Suspend! [Sibling: Loading...]', + 'Final', + 'Suspend! [Sibling: Final]', + ]), + ]); + expect(root).toMatchRenderedOutput(null); + + // The initial sibling loads, we're unblocked and commit. + await act(() => { + resolveText('Sibling: Loading...'); + }); + assertLog([ + 'Loading...', + 'Sibling: Loading...', + 'Final', + 'Suspend! [Sibling: Final]', + ]); + expect(root).toMatchRenderedOutput('Loading... Sibling: Loading...'); + + // Now unblock the final sibling. + await act(() => { + resolveText('Sibling: Final'); + }); + assertLog(['Final', 'Sibling: Final']); + expect(root).toMatchRenderedOutput('Final Sibling: Final'); + }, + ); + it( 'if a suspended render spawns a deferred task, we can switch to the ' + 'deferred task without finishing the original one (no Suspense boundary, ' + @@ -462,9 +631,12 @@ describe('ReactDeferredValue', () => { // The initial value suspended, so we attempt the final value, which // also suspends. 'Suspend! [Final]', - // pre-warming - 'Suspend! [Loading...]', - 'Suspend! [Final]', + ...(gate('enableParallelTransitions') + ? [ + // With parallel transitions, + // we do not continue pre-warming. + ] + : ['Suspend! [Loading...]', 'Suspend! [Final]']), ]); expect(root).toMatchRenderedOutput(null); @@ -539,9 +711,12 @@ describe('ReactDeferredValue', () => { // The initial value suspended, so we attempt the final value, which // also suspends. 'Suspend! [Final]', - // pre-warming - 'Suspend! [Loading...]', - 'Suspend! [Final]', + ...(gate('enableParallelTransitions') + ? [ + // With parallel transitions, + // we do not continue pre-warming. + ] + : ['Suspend! [Loading...]', 'Suspend! [Final]']), ]); expect(root).toMatchRenderedOutput(null); diff --git a/packages/react-reconciler/src/__tests__/ReactTransition-test.js b/packages/react-reconciler/src/__tests__/ReactTransition-test.js index d3ad2e613857..7a962018067a 100644 --- a/packages/react-reconciler/src/__tests__/ReactTransition-test.js +++ b/packages/react-reconciler/src/__tests__/ReactTransition-test.js @@ -209,6 +209,319 @@ describe('ReactTransition', () => { expect(root).toMatchRenderedOutput('Async'); }); + // @gate enableLegacyCache + it('when multiple transitions update different queues, they entangle', async () => { + let setA; + let startTransitionA; + let setB; + let startTransitionB; + function A() { + const [a, _setA] = useState(0); + const [isPending, _startTransitionA] = useTransition(); + setA = _setA; + startTransitionA = _startTransitionA; + + return ( + + {isPending && ( + + + + )} + + + ); + } + + function B() { + const [b, _setB] = useState(0); + const [isPending, _startTransitionB] = useTransition(); + setB = _setB; + startTransitionB = _startTransitionB; + + return ( + + {isPending && ( + + + + )} + + + ); + } + function App() { + return ( + <> + Loading A}> + + + Loading B}> + + + + ); + } + + // Initial render + const root = ReactNoop.createRoot(); + await act(() => { + root.render(); + }); + assertLog([ + 'Suspend! [A: 0]', + 'Suspend! [B: 0]', + 'Suspend! [A: 0]', + 'Suspend! [B: 0]', + ]); + expect(root).toMatchRenderedOutput( + <> + Loading A + Loading B + , + ); + + // Resolve + await act(() => { + resolveText('A: 0'); + resolveText('B: 0'); + }); + assertLog(['A: 0', 'B: 0']); + expect(root).toMatchRenderedOutput( + <> + A: 0 + B: 0 + , + ); + + // Start transitioning A + await act(() => { + startTransitionA(() => { + setA(1); + }); + }); + assertLog(['Pending A...', 'A: 0', 'Suspend! [A: 1]']); + expect(root).toMatchRenderedOutput( + <> + + Pending A...A: 0 + + B: 0 + , + ); + + // Start transitioning B + await act(() => { + startTransitionB(() => { + setB(1); + }); + }); + assertLog(['Pending B...', 'B: 0', 'Suspend! [A: 1]', 'Suspend! [B: 1]']); + expect(root).toMatchRenderedOutput( + <> + + Pending A...A: 0 + + + Pending B...B: 0 + + , + ); + + // Resolve B + await act(() => { + resolveText('B: 1'); + }); + assertLog( + gate('enableParallelTransitions') + ? ['B: 1', 'Suspend! [A: 1]'] + : ['Suspend! [A: 1]', 'B: 1'], + ); + expect(root).toMatchRenderedOutput( + gate('enableParallelTransitions') ? ( + <> + + Pending A...A: 0 + + B: 1 + + ) : ( + <> + + Pending A...A: 0 + + + Pending B...B: 0 + + + ), + ); + + // Resolve A + await act(() => { + resolveText('A: 1'); + }); + assertLog(gate('enableParallelTransitions') ? ['A: 1'] : ['A: 1', 'B: 1']); + expect(root).toMatchRenderedOutput( + <> + A: 1 + B: 1 + , + ); + }); + + // @gate enableLegacyCache + it('when multiple transitions update different queues, but suspend the same boundary, they do entangle', async () => { + let setA; + let startTransitionA; + let setB; + let startTransitionB; + function A() { + const [a, _setA] = useState(0); + const [isPending, _startTransitionA] = useTransition(); + setA = _setA; + startTransitionA = _startTransitionA; + + return ( + + {isPending && ( + + + + )} + + + ); + } + + function B() { + const [b, _setB] = useState(0); + const [isPending, _startTransitionB] = useTransition(); + setB = _setB; + startTransitionB = _startTransitionB; + + return ( + + {isPending && ( + + + + )} + + + ); + } + function App() { + return ( + Loading...}> + + + + ); + } + + // Initial render + const root = ReactNoop.createRoot(); + await act(() => { + root.render(); + }); + assertLog([ + 'Suspend! [A: 0]', + // pre-warming + 'Suspend! [A: 0]', + 'Suspend! [B: 0]', + ]); + expect(root).toMatchRenderedOutput(Loading...); + + // Resolve + await act(() => { + resolveText('A: 0'); + resolveText('B: 0'); + }); + assertLog(['A: 0', 'B: 0']); + expect(root).toMatchRenderedOutput( + <> + A: 0 + B: 0 + , + ); + + // Start transitioning A + await act(() => { + startTransitionA(() => { + setA(1); + }); + }); + assertLog(['Pending A...', 'A: 0', 'Suspend! [A: 1]']); + expect(root).toMatchRenderedOutput( + <> + + Pending A...A: 0 + + B: 0 + , + ); + + // Start transitioning B + await act(() => { + startTransitionB(() => { + setB(1); + }); + }); + assertLog(['Pending B...', 'B: 0', 'Suspend! [A: 1]', 'Suspend! [B: 1]']); + expect(root).toMatchRenderedOutput( + <> + + Pending A...A: 0 + + + Pending B...B: 0 + + , + ); + + // Resolve B + await act(() => { + resolveText('B: 1'); + }); + assertLog( + gate('enableParallelTransitions') + ? ['B: 1', 'Suspend! [A: 1]'] + : ['Suspend! [A: 1]', 'B: 1'], + ); + expect(root).toMatchRenderedOutput( + gate('enableParallelTransitions') ? ( + <> + + Pending A...A: 0 + + B: 1 + + ) : ( + <> + + Pending A...A: 0 + + + Pending B...B: 0 + + + ), + ); + + // Resolve A + await act(() => { + resolveText('A: 1'); + }); + assertLog(gate('enableParallelTransitions') ? ['A: 1'] : ['A: 1', 'B: 1']); + expect(root).toMatchRenderedOutput( + <> + A: 1 + B: 1 + , + ); + }); + // @gate enableLegacyCache it( 'when multiple transitions update the same queue, only the most recent ' + diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 113370d1eb79..affa741aa9e4 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -215,6 +215,9 @@ export const disableInputAttributeSyncing: boolean = false; // Disables children for