React hooks/set state in effect lint hir order fix#675
Conversation
Greptile SummaryThis PR fixes an evaluation order-dependence bug in Root cause: The original single-pass traversal interleaved discovery (building Fix: The traversal is split into two passes — the first pass fully populates Key changes:
Confidence Score: 4/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[validateNoSetStateInEffects] --> B[Pass 1: Discovery]
B --> C{Instruction kind?}
C -- LoadLocal --> D[Propagate setStateFunctions ID alias]
C -- StoreLocal --> E[Propagate setStateFunctions ID alias]
C -- FunctionExpression --> F[getSetStateCall on inner func body]
F --> G{Calls setState\nor known setState fn?}
G -- Yes --> H[Add lvalue to setStateFunctions]
G -- No --> I[Skip]
C -- CallExpression/MethodCall --> J{isUseEffectEventType?}
J -- Yes --> K{arg in setStateFunctions?}
K -- Yes --> L[Add lvalue to setStateFunctions]
K -- No --> I
J -- No --> I
B --> M[Pass 2: Error Reporting]
M --> N{CallExpression or MethodCall?}
N -- No --> O[Skip instruction]
N -- Yes --> P{isUseEffect / useLayoutEffect / useInsertionEffect?}
P -- No --> O
P -- Yes --> Q{arg in setStateFunctions?}
Q -- No --> O
Q -- Yes --> R{enableVerbose?}
R -- Yes --> S[Push verbose EffectSetState diagnostic]
R -- No --> T[Push standard EffectSetState diagnostic]
Last reviewed commit: 20d7ab6 |
| const [, event] = logs.at(0)!; | ||
| expect(event).toBeDefined(); |
There was a problem hiding this comment.
Unclear failure message if logs array is empty
If the fix does not fire (i.e., logs is empty), logs.at(0) returns undefined at runtime. The ! non-null assertion only suppresses the TypeScript error — at runtime destructuring undefined throws a TypeError: Cannot destructure property... rather than a readable Jest assertion failure.
Adding an explicit length check before the destructure would produce a much clearer failure message if this test ever regresses:
| const [, event] = logs.at(0)!; | |
| expect(event).toBeDefined(); | |
| expect(logs.length).toBeGreaterThan(0); | |
| const [, event] = logs.at(0)!; | |
| expect(event).toBeDefined(); |
Greptile SummaryThis PR fixes a HIR block-iteration-order bug in
Confidence Score: 4/5
Important Files Changed
Last reviewed commit: 20d7ab6 |
| const [, event] = logs.at(0)!; | ||
| expect(event).toBeDefined(); | ||
| expect(event.kind).toBe('CompileError'); | ||
| expect(event.detail.category).toBe(ErrorCategory.EffectSetState); |
There was a problem hiding this comment.
Missing type narrowing before accessing event.detail
event is typed as LoggerEvent, which is a union of 7 variants (CompileSuccessEvent, CompileErrorEvent, CompileSkipEvent, etc.). Only CompileErrorEvent and CompileDiagnosticEvent have a detail property. Without narrowing the discriminant first, TypeScript will report an error when accessing event.detail and event.detail.category.
Every other test in this file uses invariant(event.kind === 'CompileError', 'typescript be smarter') to narrow the type before making assertions on event.detail. The same pattern should be applied here.
| const [, event] = logs.at(0)!; | |
| expect(event).toBeDefined(); | |
| expect(event.kind).toBe('CompileError'); | |
| expect(event.detail.category).toBe(ErrorCategory.EffectSetState); | |
| const [, event] = logs.at(0)!; | |
| expect(event).toBeDefined(); | |
| expect(event.kind).toBe('CompileError'); | |
| invariant(event.kind === 'CompileError', 'typescript be smarter'); | |
| expect(event.detail.category).toBe(ErrorCategory.EffectSetState); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: compiler/packages/babel-plugin-react-compiler/src/__tests__/Logger-test.ts
Line: 103-106
Comment:
**Missing type narrowing before accessing `event.detail`**
`event` is typed as `LoggerEvent`, which is a union of 7 variants (`CompileSuccessEvent`, `CompileErrorEvent`, `CompileSkipEvent`, etc.). Only `CompileErrorEvent` and `CompileDiagnosticEvent` have a `detail` property. Without narrowing the discriminant first, TypeScript will report an error when accessing `event.detail` and `event.detail.category`.
Every other test in this file uses `invariant(event.kind === 'CompileError', 'typescript be smarter')` to narrow the type before making assertions on `event.detail`. The same pattern should be applied here.
```suggestion
const [, event] = logs.at(0)!;
expect(event).toBeDefined();
expect(event.kind).toBe('CompileError');
invariant(event.kind === 'CompileError', 'typescript be smarter');
expect(event.detail.category).toBe(ErrorCategory.EffectSetState);
```
How can I resolve this? If you propose a fix, please make it concise.
Mirror of facebook/react#35988
Original author: mugicaj
Summary
PR references this issue
The HIR evaluation in ValidateNoSetStateInEffect.ts suffers from evaluation order dependence. The validator has to:
This means that if the callback for onSetState (which is a useEffectEvent function) is not evaluated by the time the useEffect hook is being evaluated, it will erroneously not throw "EffectSetState".
In order to solve this we can split the node discovery and the error reporting into 2 separate passes which ensures that by the time we are evaluating the errors we have a complete view of the HIR function, as opposed to reporting as we traverse it.
How did you test this change?
Test: