Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/fix-use-memo-result.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"react-19-remove-memoization": patch
---

Replace `useMemo(factory, deps)` with the factory's result instead of the factory function, so memoized values
stay values. Only unwrap parameterless factories; keep return-type annotations and `useMemo<T>` typing; invoke
member factories unbound via `(0, obj.fn)()`; convert nested memoization inside-out; and remove unused import
bindings by local alias so duplicate `useMemo as a` / `useMemo as b` specifiers stay independent. Preserve
`import type` and semicolon style on React imports, and leave imports that lose no specifier untouched.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
node_modules/
.pnpm-store/
dist/
*.log
.DS_Store
Expand Down
15 changes: 15 additions & 0 deletions codemods/remove-memoization/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ Remove manual React memoization calls: `useCallback(...)`, `useMemo(...)`, and `

This codemod is intended for React Compiler adoption and mirrors `react/19/remove-memoization` from `codemod/commons`.

`useCallback(fn, deps)` and `memo(Component)` are replaced by their first argument. `useMemo(factory, deps)`
is replaced by the factory's *result*:

- a parameterless expression-bodied arrow becomes its body (`useMemo(() => x, deps)` → `x`)
- a parameterless block that only returns an expression does the same (`useMemo(() => { return x; }, deps)` → `x`)
- arrows with parameters, `async` factories, and other bodies are invoked in place (`((x = f()) => x)()`, `(async () => …)()`)
- return-type annotations stay on an IIFE; `useMemo<T>(…)` without a return annotation keeps `T` via `as T`
- member factories are invoked unbound (`useMemo(obj.fn, [])` → `(0, obj.fn)()`) to match React’s call
- nested memoization is converted inside-out (`useMemo(useMemo(() => () => 42))` → `42`)

Only React imports that lose a specifier are rewritten; `import type` and the file's semicolon style are preserved.
Unused memoization bindings are removed by local alias, so duplicate aliases of the same export
(`useMemo as a` vs `useMemo as b`) are handled independently. A default or namespace React import is removed only
when the removed memoization calls were its last uses.

> This is not a universally safe codemod. React Compiler is not a 1:1 replacement for every manual `useMemo`, `useCallback`, or `memo` occurrence, so some memoization may need to be kept to preserve semantics.

## Usage
Expand Down
Loading