Autogenerated summaries of each of the compiler passes which allow agents to get the key ideas of a compiler pass, including key input/output invariants, without having to reprocess the file each time. In the subsequent diff this seemed to help. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35595). * #35607 * #35298 * #35596 * #35573 * __->__ #35595 * #35539
5.0 KiB
validateStaticComponents
File
src/Validation/ValidateStaticComponents.ts
Purpose
Validates that components used in JSX are not created dynamically during render. Components created during render will have their state reset on every re-render because React sees them as new component types each time. This is a common React anti-pattern that causes bugs and poor performance.
Input Invariants
- Operates on HIRFunction (pre-reactive transformation)
- All instructions and phi nodes are present
- JSX expressions have been lowered to
JsxExpressioninstruction values
Validation Rules
When a JSX element uses a component that was dynamically created during render, the pass produces:
Cannot create components during render. Components created during render will reset
their state each time they are created. Declare components outside of render
The error includes two locations:
- Where the component is used in JSX
- Where the component was originally created
What constitutes "dynamically created"?
The following instruction kinds mark a value as dynamically created:
FunctionExpression- An inline function definitionNewExpression- Anewconstructor callMethodCall- A method call that returns a valueCallExpression- A function call that returns a value
Algorithm
-
Create a
Map<IdentifierId, SourceLocation>calledknownDynamicComponentsto track identifiers whose values are dynamically created -
Iterate through all blocks in evaluation order
-
For each block, first process phi nodes:
- If any phi operand is in
knownDynamicComponents, add the phi result to the map - This propagates dynamic-ness through control flow joins
- If any phi operand is in
-
For each instruction in the block:
- FunctionExpression, NewExpression, MethodCall, CallExpression: Add the lvalue to
knownDynamicComponentswith its source location - LoadLocal: If the loaded value is dynamic, mark the lvalue as dynamic
- StoreLocal: If the stored value is dynamic, mark both the lvalue and the store target as dynamic
- JsxExpression: If the JSX tag is an identifier that is in
knownDynamicComponents, push a diagnostic error
- FunctionExpression, NewExpression, MethodCall, CallExpression: Add the lvalue to
-
Return the collected errors
Data Flow Tracking
The pass tracks how dynamic values flow through the program:
- Through variable assignments (
StoreLocal,LoadLocal) - Through phi nodes (conditional assignments)
- Into JSX component positions
Edge Cases
Conditionally Assigned Components
function Example({cond}) {
let Component;
if (cond) {
Component = createComponent(); // Dynamic!
} else {
Component = OtherComponent; // Static
}
return <Component />; // Error: Component may be dynamic
}
The phi node joins the conditional paths, and since one path is dynamic, the result is considered dynamic.
Component Returned from Hooks/Functions
function Example() {
const Component = useCreateComponent(); // CallExpression - dynamic
return <Component />; // Error
}
Factory Functions
function Example() {
const Component = createComponent(); // CallExpression - dynamic
return <Component />; // Error
}
Safe Patterns (No Error)
// Component defined outside render
const MyComponent = () => <div />;
function Example() {
return <MyComponent />; // OK - not created during render
}
TODOs
None found in the source.
Example
Fixture: static-components/invalid-dynamically-construct-component-in-render.js
Input:
// @validateStaticComponents
function Example(props) {
const Component = createComponent();
return <Component />;
}
Error (from logs):
{
"kind": "CompileError",
"detail": {
"options": {
"category": "StaticComponents",
"reason": "Cannot create components during render",
"description": "Components created during render will reset their state each time they are created. Declare components outside of render",
"details": [
{
"kind": "error",
"loc": { "start": { "line": 4, "column": 10 } },
"message": "This component is created during render"
},
{
"kind": "error",
"loc": { "start": { "line": 3, "column": 20 } },
"message": "The component is created during render here"
}
]
}
}
}
Why it fails: The createComponent() call creates a new component type on every render. When this component is used in JSX, React will see a different component type each time, causing the component to unmount and remount (losing all state) on every render.
Fixture: static-components/invalid-dynamically-constructed-component-function.js
Input:
// @validateStaticComponents
function Example(props) {
const Component = () => <div />;
return <Component />;
}
Why it fails: Even though this looks like a simple component definition, it creates a new function (and thus a new component type) on every render. The fix is to move the component definition outside of Example.