-
Notifications
You must be signed in to change notification settings - Fork 79
refactor(FR-2616): rely on React Compiler ('use memo') for memoization #6857
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
graphite-app
merged 1 commit into
main
from
04-22-refactor_fr-2616_rely_on_react_compiler_use_memo_for_memoization
Apr 23, 2026
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| --- | ||
| description: Rely on the React Compiler (`'use memo'`) instead of manual `useMemo` / `useCallback` | ||
| paths: | ||
| - "react/**/*.{tsx,ts}" | ||
| - "packages/backend.ai-ui/**/*.{tsx,ts}" | ||
| --- | ||
|
|
||
| # React Compiler Memoization Rule | ||
|
|
||
| This project runs **React 19.2** with **`babel-plugin-react-compiler` in annotation mode**. The compiler owns memoization. Add the `'use memo'` directive to each component and hook body, and write plain values and plain functions. | ||
|
|
||
| ## Why | ||
|
|
||
| - Manual `useMemo` / `useCallback` dependency arrays drift out of sync as code evolves; the compiler's view is always current with the code it compiles. | ||
| - `useCallback` forces every identity-sensitive caller (child components, other hooks) to also memoize. The compiler short-circuits this cascade. | ||
| - Consistent adoption keeps the memoization story legible across the codebase — reviewers don't decide case-by-case whether a given `useCallback` is load-bearing or ceremonial. | ||
|
|
||
| ## Rules | ||
|
|
||
| 1. **Add `'use memo'` at the top of every component body and every custom hook body** that would benefit from memoization. This is almost all of them. Never remove an existing `'use memo'` directive. | ||
| 2. **Do not introduce `useMemo` or `useCallback`** in new code. Replace existing usages with plain values and plain functions when you touch them, and add `'use memo'` if the enclosing function doesn't already have it. | ||
| 3. **Callbacks passed to JSX** (`onClick`, `onChange`, `action`, `onSubmit`, …) must be plain inline or named functions. The compiler memoizes them. | ||
| 4. **Values derived from props / state** should be plain `const` declarations. If the derivation is expensive *and* measurably slow, consider extracting to a separate module or a hook — but still no `useMemo`. | ||
| 5. **Exception — `useEffectEvent`**: when you need a callback inside an effect that should read the latest props/state without triggering re-runs, use `useEffectEvent` (see `use-effect-event.md`). This is complementary to `'use memo'` and does not replace it. | ||
| 6. **Exception — identity-sensitive external APIs**: if a third-party API requires a stable reference across renders (e.g., a subscription that can only be installed once), prefer `useRef` or an effect-scoped closure over `useCallback`. `useCallback`'s identity guarantees are still weaker than the compiler's. | ||
|
|
||
| ## Pattern | ||
|
|
||
| ### ✅ Correct — plain functions, `'use memo'` directive | ||
|
|
||
| ```tsx | ||
| const UserProfile: React.FC<Props> = ({ user, onSelect }) => { | ||
| 'use memo'; | ||
| const { t } = useTranslation(); | ||
|
|
||
| const fullName = `${user.firstName} ${user.lastName}`; | ||
|
|
||
| const handleClick = () => { | ||
| onSelect(user.id); | ||
| }; | ||
|
|
||
| return ( | ||
| <button onClick={handleClick}> | ||
| {t('userProfile.Greet', { name: fullName })} | ||
| </button> | ||
| ); | ||
| }; | ||
| ``` | ||
|
|
||
| ### ✅ Correct — custom hook with plain returned functions | ||
|
|
||
| ```tsx | ||
| export const useSomething = (): [Value, (next: Value) => void] => { | ||
| 'use memo'; | ||
| const [value, setValue] = useState<Value>(initial); | ||
|
|
||
| const update = (next: Value) => { | ||
| setValue(next); | ||
| // side effects here, reading latest closure values | ||
| }; | ||
|
|
||
| return [value, update]; | ||
| }; | ||
| ``` | ||
|
|
||
| ### ❌ Wrong — manual `useCallback` wrapping a simple handler | ||
|
|
||
| ```tsx | ||
| const UserProfile: React.FC<Props> = ({ user, onSelect }) => { | ||
| // no 'use memo' | ||
| const handleClick = useCallback(() => { | ||
| onSelect(user.id); | ||
| }, [onSelect, user.id]); | ||
|
|
||
| return <button onClick={handleClick}>...</button>; | ||
| }; | ||
| ``` | ||
|
|
||
| ### ❌ Wrong — `useMemo` for trivial derivations | ||
|
|
||
| ```tsx | ||
| const UserProfile: React.FC<Props> = ({ user }) => { | ||
| // no 'use memo' | ||
| const fullName = useMemo( | ||
| () => `${user.firstName} ${user.lastName}`, | ||
| [user.firstName, user.lastName], | ||
| ); | ||
| return <span>{fullName}</span>; | ||
| }; | ||
| ``` | ||
|
|
||
| ## Migration | ||
|
|
||
| When editing a file that contains `useMemo` or `useCallback`: | ||
|
|
||
| 1. Add `'use memo'` to the enclosing component / hook body if it is not already there. | ||
| 2. Unwrap each `useMemo(() => expr, deps)` into `const value = expr;`. | ||
| 3. Unwrap each `useCallback(fn, deps)` into the original function body, as a plain `const fn = (...)` or inline in JSX. | ||
| 4. Drop the `useMemo` / `useCallback` import if it becomes unused. | ||
| 5. Run `bash scripts/verify.sh` — the React Compiler is part of the build pipeline; if the compiler cannot safely memoize something (e.g., mutation in a closure), the lint rule `react-hooks/*` or the build itself will surface the problem. | ||
|
|
||
| ## Verification | ||
|
|
||
| After editing a memoization-relevant file, confirm: | ||
|
|
||
| - `'use memo'` appears on the first line of the component / hook body. | ||
| - No new `useMemo` or `useCallback` imports or calls were introduced. | ||
| - `bash scripts/verify.sh` passes — the compiler is exercised during `pnpm run lint` and the TypeScript build. | ||
| - For hooks: existing call sites still behave correctly. The compiler's output is observably equivalent to the hand-rolled version in almost every case; if not, the compiler surfaces a diagnostic. | ||
|
|
||
| ## Related | ||
|
|
||
| - `use-effect-event.md` — companion rule for the `useEffectEvent` primitive used inside effects. | ||
| - `CLAUDE.md` → "React Essentials" — project-wide React conventions, including the `'use memo'` requirement. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.