| description | Rely on the React Compiler (`'use memo'`) instead of manual `useMemo` / `useCallback` | ||
|---|---|---|---|
| paths |
|
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.
- Manual
useMemo/useCallbackdependency arrays drift out of sync as code evolves; the compiler's view is always current with the code it compiles. useCallbackforces 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
useCallbackis load-bearing or ceremonial.
- 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. - Do not introduce
useMemooruseCallbackin 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. - Callbacks passed to JSX (
onClick,onChange,action,onSubmit, …) must be plain inline or named functions. The compiler memoizes them. - Values derived from props / state should be plain
constdeclarations. If the derivation is expensive and measurably slow, consider extracting to a separate module or a hook — but still nouseMemo. - Exception —
useEffectEvent: when you need a callback inside an effect that should read the latest props/state without triggering re-runs, useuseEffectEvent(seeuse-effect-event.md). This is complementary to'use memo'and does not replace it. - 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
useRefor an effect-scoped closure overuseCallback.useCallback's identity guarantees are still weaker than the compiler's.
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>
);
};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];
};const UserProfile: React.FC<Props> = ({ user, onSelect }) => {
// no 'use memo'
const handleClick = useCallback(() => {
onSelect(user.id);
}, [onSelect, user.id]);
return <button onClick={handleClick}>...</button>;
};const UserProfile: React.FC<Props> = ({ user }) => {
// no 'use memo'
const fullName = useMemo(
() => `${user.firstName} ${user.lastName}`,
[user.firstName, user.lastName],
);
return <span>{fullName}</span>;
};When editing a file that contains useMemo or useCallback:
- Add
'use memo'to the enclosing component / hook body if it is not already there. - Unwrap each
useMemo(() => expr, deps)intoconst value = expr;. - Unwrap each
useCallback(fn, deps)into the original function body, as a plainconst fn = (...)or inline in JSX. - Drop the
useMemo/useCallbackimport if it becomes unused. - 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 rulereact-hooks/*or the build itself will surface the problem.
After editing a memoization-relevant file, confirm:
'use memo'appears on the first line of the component / hook body.- No new
useMemooruseCallbackimports or calls were introduced. bash scripts/verify.shpasses — the compiler is exercised duringpnpm run lintand 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.
use-effect-event.md— companion rule for theuseEffectEventprimitive used inside effects.CLAUDE.md→ "React Essentials" — project-wide React conventions, including the'use memo'requirement.