You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -32,6 +32,8 @@ A quick way to identify them is to install the [React Compiler Marker VSCode ext
32
32
33
33
Once fixed, the file's entry will be removed from `.react-compiler.rec.json`.
34
34
35
+
> **For LLM agents:** If a file is not in `.react-compiler.rec.json`, do not add `useMemo`, `useCallback`, or `React.memo` — the compiler handles memoization automatically. See [For LLM agents](#for-llm-agents) for the full workflow.
**Note:** The lazy initializer captures the initial prop value. If `store` is initially undefined but becomes defined later, `storeInstance` will remain the created store. This matches the original ref behavior and is typically intentional.
99
-
100
100
**Before (one-time value computation):**
101
101
102
102
```typescript
@@ -208,7 +208,7 @@ function ProjectBoardView({ showCompleted: showCompletedProp }) {
208
208
>
209
209
> Reason: (BuildHIR::node.lowerReorderableExpression) Expression type `OptionalMemberExpression` cannot be safely reordered
210
210
211
-
The compiler can't safely reorder default parameter values that reference other parameters. This applies to both regular member access (`task.id`) and optional chaining (`task?.id`), since both depend on a sibling parameter being evaluated first.
211
+
The compiler can't safely reorder default parameter values that reference other parameters.
> Reason: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
411
411
412
-
The `store?.useState()` pattern conditionally calls a hook - when `store` is `undefined`, the hook isn't called; when defined, it is. This violates the Rules of Hooks which require hooks to be called unconditionally in the same order on every render.
412
+
The `store?.useState()` pattern conditionally calls a hook, violating the Rules of Hooks.
The inner function declaration is hoisted within its scope, allowing self-reference without depending on the outer variable. This pattern works for both `useEvent` and `useCallback`.
500
500
501
-
**Warning: Stale closures with async operations.** The inner function captures state values when the outer function is invoked. If the inner function is called later via `setTimeout`, it will see stale values. Use `useRef` instead for values read across async callbacks (like retry counters), since `.current` is read at access time rather than captured in the closure.
501
+
**Warning:** The inner function captures state values at invocation time. For valuesread across async callbacks (like retry counters), use `useRef` instead.
502
502
503
503
#### Avoiding self-reference entirely
504
504
@@ -529,7 +529,206 @@ useEffect(
529
529
)
530
530
```
531
531
532
-
### Optional chaining in try/catch blocks
532
+
### Try/catch blocks
533
+
534
+
React Compiler has limited support for try/catch statements. Several patterns cause violations:
535
+
536
+
> **Note:** A fix for some of these limitations has been merged and may be available in a future compiler release. See [facebook/react#35606](https://github.com/facebook/react/pull/35606).
537
+
538
+
#### Try-catch-finally
539
+
540
+
> Reason: (BuildHIR::lowerStatement) Handle TryStatement with a finalizer ('finally') clause
541
+
542
+
The `finally` clause causes compiler violations. Remove `finally` and explicitly handle cleanup in all code paths.
> Reason: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
592
+
593
+
Try statements must have a `catch` clause. A `try { } finally { }` without `catch` is not supported. Additionally, since `finally` itself can cause violations (see above), consider removing it entirely.
594
+
595
+
**Before:**
596
+
597
+
```typescript
598
+
useEffect(function loadData() {
599
+
;(async () => {
600
+
try {
601
+
const data =awaitfetchData()
602
+
setState(data)
603
+
} finally {
604
+
setIsLoading(false)
605
+
}
606
+
})().catch(() =>setError('Failed'))
607
+
}, [])
608
+
```
609
+
610
+
**After:**
611
+
612
+
```typescript
613
+
useEffect(function loadData() {
614
+
;(async () => {
615
+
try {
616
+
const data =awaitfetchData()
617
+
setState(data)
618
+
setIsLoading(false)
619
+
} catch {
620
+
setError('Failed')
621
+
setIsLoading(false)
622
+
}
623
+
})()
624
+
}, [])
625
+
```
626
+
627
+
#### ThrowStatement inside try/catch
628
+
629
+
> Reason: ThrowStatement inside try/catch not yet supported
630
+
631
+
Throwing errors inside try blocks causes violations. Handle errors directly instead of using throw.
When calling a function that already handles errors internally and returns a safe fallback, wrapping it in another try/catch is redundant and may cause violations.
669
+
670
+
**Before:**
671
+
672
+
```typescript
673
+
// getLocalStorageValue already has internal try/catch, returns undefined on error
674
+
function getLocalStorageValue<T>(key:string):T|undefined {
675
+
try {
676
+
const item =localStorage.getItem(key)
677
+
returnitem?JSON.parse(item) :undefined
678
+
} catch {
679
+
returnundefined
680
+
}
681
+
}
682
+
683
+
const [value, setValue] =useState<T>(() => {
684
+
try {
685
+
returngetLocalStorageValue(key) ??defaultValue
686
+
} catch {
687
+
returndefaultValue
688
+
}
689
+
})
690
+
```
691
+
692
+
**After:**
693
+
694
+
```typescript
695
+
// getLocalStorageValue already handles errors, no outer try/catch needed
> Reason: Support value blocks (conditional, logical, optional chaining, etc) within a try/catch statement
702
+
703
+
Conditional expressions (`? :`), logical operators (`&&`, `||`), nullish coalescing (`??`), and other "value blocks" inside try/catch cause violations. The general fix is to extract the try/catch logic into a helper function outside the component.
704
+
705
+
**Before:**
706
+
707
+
```typescript
708
+
const value =useMemo(() => {
709
+
try {
710
+
returnriskyOperation() ??fallback
711
+
} catch {
712
+
returnfallback
713
+
}
714
+
}, [deps])
715
+
```
716
+
717
+
**After:**
718
+
719
+
```typescript
720
+
function safeRiskyOperation(deps:Deps) {
721
+
try {
722
+
returnriskyOperation(deps) ??fallback
723
+
} catch {
724
+
returnfallback
725
+
}
726
+
}
727
+
728
+
const value =useMemo(() =>safeRiskyOperation(deps), [deps])
729
+
```
730
+
731
+
#### Optional chaining in try/catch blocks
533
732
534
733
> Reason: Support value blocks (conditional, logical, optional chaining, etc) within a try/catch statement
## Identifying violations and verifying fixes (for LLMs)
921
+
## For LLM agents
922
+
923
+
When working on React components or hooks in this codebase, follow this workflow:
924
+
925
+
### 1. Check if the file needs attention
724
926
725
-
When fixing violations programmatically, first identify modules with violations by checking `.react-compiler.rec.json`, then use the tracker CLI with `--show-errors` to see the exact errors:
927
+
Look up the file in `.react-compiler.rec.json`:
928
+
929
+
-**Not listed** → Compiler is optimizing it. Do NOT add `useMemo`, `useCallback`, or `React.memo`.
930
+
-**Listed with errors** → Continue to step 2.
931
+
932
+
### 2. Identify violations
933
+
934
+
Run the tracker with `--show-errors` to see exact errors:
- src/path/to/file.tsx: Line 28: Existing memoization could not be preserved (x2)
740
949
```
741
950
742
-
Parse the output to extract error reasons (e.g., "Cannot access refs during render") and line numbers to plan the fix.
951
+
Parse the output to extract error reasons and line numbers to plan the fix.
952
+
953
+
### 3. Fix violations
743
954
744
-
Once the fix is applied, verify it using one of the following methods:
955
+
Use the patterns in [Common errors](#common-errors) to fix each violation. Focus on making the code compiler-compatible rather than adding more manual memoization.
745
956
746
-
**1. CLI Tool (recommended)**
957
+
### 4. Verify the fix
747
958
748
-
Run the tracker with `--check-files` to verify changes against the records file:
0 commit comments