Skip to content

Commit fa6b06c

Browse files
authored
chore: Update React development guidelines (#1023)
This file is automatically synced from the `shared-configs` repository. Source: https://github.com/doist/shared-configs/blob/main/
1 parent 2422780 commit fa6b06c

1 file changed

Lines changed: 57 additions & 0 deletions

File tree

docs/react-guidelines/react-compiler.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ If you find existing `useMemo` or `useCallback` calls in compiler-enabled code,
1717
> - The component still has compiler violations (check `.react-compiler.rec.json`)
1818
> - The compiler is not enabled in the project
1919
> - The memoized value is defined before other hook calls that separate it from its hook consumer (e.g., `useState`, `useEffect`). The compiler silently skips memoization in this case, even though the file compiles without errors. See [Memoization gap with intervening hooks](#memoization-gap-with-intervening-hooks).
20+
> - The value comes from a `use`-prefixed function that contains no actual hook calls. The compiler treats these as hooks and cannot cache their return values. See [Hook-named helper functions prevent return-value memoization](#hook-named-helper-functions-prevent-return-value-memoization).
2021
2122
## Workflow: Identifying and fixing violations
2223

@@ -1146,6 +1147,62 @@ const { result } = renderHook(() => useMyHook(options))
11461147
expect(result.current.someValue).toBe(expected)
11471148
```
11481149
1150+
### Hook-named helper functions prevent return-value memoization
1151+
1152+
> This is not a compiler error. The file compiles cleanly and passes
1153+
> `react-compiler-tracker --check-files`, but the compiler silently skips
1154+
> memoization it would otherwise apply.
1155+
1156+
Any function named `use` + uppercase letter is treated as a hook, even if it contains no hook calls. This causes two missed optimizations:
1157+
1158+
1. **At the call site**, the compiler must invoke hooks unconditionally every render (Rules of Hooks), so the return value cannot be cached.
1159+
2. **At the definition site**, the compiler [skips functions that look like hooks but contain no hook calls or JSX](https://github.com/facebook/react/issues/31727). The function body is never compiled, so expressions inside it (like array literals) are never memoized either.
1160+
1161+
**Fix:** Rename the function to `get`, `create`, `build`, or another non-hook prefix at the definition/export site.
1162+
1163+
```typescript
1164+
// Before
1165+
export function useMembersTableColumns({ workspaceId, isMembersActivityLoading }) { ... }
1166+
// After (rename at the definition site)
1167+
export function getMembersTableColumns({ workspaceId, isMembersActivityLoading }) { ... }
1168+
```
1169+
1170+
The compiled output shows the difference. With the `use` prefix, the argument is cached but the call runs unconditionally:
1171+
1172+
```javascript
1173+
// Compiled: useMembersTableColumns (argument cached, call is not)
1174+
let t2
1175+
if ($[4] !== isMembersActivityLoading || $[5] !== workspaceId) {
1176+
t2 = { workspaceId, isMembersActivityLoading }
1177+
$[4] = isMembersActivityLoading
1178+
$[5] = workspaceId
1179+
$[6] = t2
1180+
} else {
1181+
t2 = $[6]
1182+
}
1183+
const columns = useMembersTableColumns(t2) // ← called every render
1184+
```
1185+
1186+
With a plain name, the entire call moves inside the cache block:
1187+
1188+
```javascript
1189+
// Compiled: getMembersTableColumns (entire call cached)
1190+
let t2
1191+
if ($[4] !== isMembersActivityLoading || $[5] !== workspaceId) {
1192+
t2 = getMembersTableColumns({ workspaceId, isMembersActivityLoading })
1193+
$[4] = isMembersActivityLoading
1194+
$[5] = workspaceId
1195+
$[6] = t2
1196+
} else {
1197+
t2 = $[6]
1198+
}
1199+
const columns = t2 // ← stable reference when inputs unchanged
1200+
```
1201+
1202+
> **Import aliases don't help.** The compiler resolves the original export
1203+
> name, not the local alias. `import { useFoo as getFoo }` still treats
1204+
> the call as a hook. The rename must happen at the definition/export site.
1205+
11491206
### Memoization gap with intervening hooks
11501207
11511208
> This is not a compiler error — the file compiles cleanly and passes

0 commit comments

Comments
 (0)