A codemod that removes useMemo and useCallback from your React codebase so you can adopt React Compiler without the manual cleanup.
Note: React Compiler works fine with existing manual memoization — it preserves and layers on top of it. Removing hooks is optional for cleanliness and readability, but not required for performance. Use this tool to declutter legacy code. Always preview with
--dry-runand type-check afterward.
npx react-compiler-unmemo ./my-react-appThis previews all changes without modifying any files (dry-run is the safe default). When you're ready to apply:
npx react-compiler-unmemo ./my-react-app --writeOr clone and run locally:
git clone https://github.com/thinksharpe/react-compiler-unmemo.git
cd react-compiler-unmemo
npm install
node react-compiler-unmemo.mjs ./path/to/your/projectI tried using Claude Opus 4.5 to write a script that would remove useMemo and useCallback from my codebase, but it kept failing. Claude noticed that sed couldn't handle this kind of complex pattern matching — the nested parentheses, arrow functions, and dependency arrays made it impossible with simple text replacement. So it wrote this script instead.
I like to keep my code as readable as possible, and all those useMemo and useCallback hooks were adding extra complexity. I'm glad they're gone from my codebase now. Hope it helps someone else too.
- const value = useMemo(() => computeExpensiveValue(a, b), [a, b]);
+ const value = computeExpensiveValue(a, b);
- const handler = useCallback((e) => doSomething(e), [doSomething]);
+ const handler = (e) => doSomething(e);
- import { useMemo, useCallback, useState } from "react";
+ import { useState } from "react";- Cleaner code — less boilerplate, easier to read and maintain
- Aligns with modern React — React Compiler handles memoization automatically
- Smaller bundles — unused hook imports are removed
- Safe to re-run — idempotent, won't touch already-processed files
# 1. Preview changes (safe default — no files modified)
node react-compiler-unmemo.mjs ./my-app
# 2. Apply changes
node react-compiler-unmemo.mjs ./my-app --write
# 3. Type-check your project
cd ./my-app && npx tsc --noEmit| Flag | Description | Default |
|---|---|---|
| (no flag) | Preview changes without writing files | dry-run |
--write |
Apply changes to files | off |
--verbose |
Log every transformation | off |
--files <glob> |
Limit to specific file patterns | src/**/*.{tsx,ts} |
--skip-fix |
Skip type annotation repair step | off |
# Only process hooks directory
node react-compiler-unmemo.mjs ./my-app --files "src/hooks/**/*.ts" --write
# app directory
node react-compiler-unmemo.mjs ./my-app --files "app/**/*.{tsx,ts}"
# See every change in detail
node react-compiler-unmemo.mjs ./my-app --verboseuseMemo(() => expr, [deps])→expruseMemo(() => { return expr; }, [deps])→expruseMemo<Type>(...)→ strips the generic, restores the type annotationuseCallback((params) => body, [deps])→(params) => bodyReact.useMemo/React.useCallbackvariants- Multi-line hooks spanning dozens of lines
- Nested generics like
useMemo<ColumnsType<MyType>>() - Import cleanup (removes unused
useMemo/useCallbackfrom imports)
- You haven't enabled React Compiler yet — without the compiler, removing
useMemo/useCallbackmay cause performance regressions - Intentional escape hatches — some hooks are used deliberately to control referential identity for third-party libraries
- Class component interop — if memoized values are passed to class components that rely on shallow comparison
When in doubt, use --dry-run and review the output.
Always run your type checker after the migration:
npx tsc --noEmit
# or your project's build commandThe tool handles the most common cases automatically, but some things need manual attention:
- Hooks with
//comments inside — the parser may skip or partially transform these. Check the "remaining refs" count in the output. - Missing imports — the type fixer adds
ColumnsType<T>annotations but may not add the import statement. - Dangling
as Typecasts — if auseCallbackhad anas React.FCcast, it may need cleanup. - Broken IIFEs — complex
useMemobodies with comments may leave a trailing, [deps])instead of})().
See docs/edge-cases.md for the full list with code examples.
react-compiler-unmemo/
├── react-compiler-unmemo.mjs # Entry point
├── helpers/
│ ├── remove-hooks.mjs # Core hook removal
│ └── fix-type-annotations.mjs
├── docs/
│ ├── architecture.md # How it works under the hood
│ └── edge-cases.md # Known edge cases & fixes
├── package.json
└── README.md
For technical details on the parser and pipeline, see docs/architecture.md.
Issues, PRs, and edge-case examples welcome. Please open an issue first for large changes.
If you've run this on a real codebase and hit an edge case, sharing the pattern (even without proprietary code) helps improve the tool for everyone.
MIT — see LICENSE