Every React Native developer has written useCallback "just in case" to stop a list row from re-rendering. The React Compiler makes most of that unnecessary. It analyzes your components at build time and memoizes them automatically, so the right things re-render and nothing else does. It's stable, it works with React Native, and Expo makes enabling it a one-line change.
What the React Compiler does
The compiler is a Babel plugin that rewrites your components so that:
- JSX, computed values, and callbacks are cached and only recomputed when their inputs change.
- Child components skip re-rendering when their props haven't changed, without
React.memo. - Inline functions and objects stay referentially stable, which keeps
FlatListandFlashListrows from re-rendering.
You write plain React, and the output behaves as if you had wrapped everything correctly in useMemo and useCallback, without the bugs from missing dependencies.
Enable it in Expo
On Expo SDK 54 and later, Babel is configured for you. Turn it on in app.json:
{
"expo": {
"experiments": {
"reactCompiler": true
}
}
}
Restart the bundler with a clean cache:
npx expo start --clear
Check your codebase first
Before turning it on in a large app, run the health check:
npx react-compiler-healthcheck@latest
It reports how many components the compiler can optimize and flags patterns it can't. On SDK 55+, React Compiler lint rules are included in eslint-config-expo, so npx expo lint shows problems inline.
Before and after
Before, manual memoization everywhere:
const ProductRow = React.memo(function ProductRow({ product, onAdd }: Props) {
return <Row title={product.title} onPress={() => onAdd(product.id)} />
})
export function ProductList({ products }: { products: Product[] }) {
const addToCart = useCart((s) => s.add)
const sorted = useMemo(() => [...products].sort(byPrice), [products])
const handleAdd = useCallback((id: string) => addToCart(id), [addToCart])
return (
<FlashList
data={sorted}
renderItem={({ item }) => <ProductRow product={item} onAdd={handleAdd} />}
/>
)
}
After, plain React. The compiler handles it:
function ProductRow({ product, onAdd }: Props) {
return <Row title={product.title} onPress={() => onAdd(product.id)} />
}
export function ProductList({ products }: { products: Product[] }) {
const addToCart = useCart((s) => s.add)
const sorted = [...products].sort(byPrice)
return (
<FlashList
data={sorted}
renderItem={({ item }) => <ProductRow product={item} onAdd={addToCart} />}
/>
)
}
There's less code, no dependency arrays to get wrong, and the same or better performance.
Opt out a component
If a component misbehaves, add "use no memo" as the first statement to skip it:
function LegacyChart(props: ChartProps) {
"use no memo"
// ...
}
Use it as a temporary escape hatch while you fix the underlying issue.
What breaks the compiler (the Rules of React)
The compiler assumes your components are pure. Code that worked "by accident" can behave differently:
- Mutating props, state, or objects during render, like
items.push(x)inside the component body. Create new arrays instead. - Reading or writing
ref.currentduring render. Do it in effects or event handlers. - Conditional hooks or hooks called in loops.
- Relying on a component re-rendering to pick up a mutable global value.
If the compiler can't prove a component is safe, it skips it rather than breaking it, so the main risk is in code that violates the rules in ways it can't detect.
Should you remove existing useMemo and useCallback?
- New code: don't add them by default.
- Existing code: leave them. They're harmless with the compiler on. Remove them gradually in files you're already editing.
- Keep them for values that must stay stable for third-party APIs or effect dependencies, until you've verified behavior.
Measuring the impact
- Use React Native DevTools' profiler to compare re-render counts before and after.
- Watch for smoother list scrolling and typing in forms (where wasted re-renders hurt most).
- Test a release build. Dev mode adds overhead that hides real gains.
Pairs well with
The compiler fixes wasted re-renders. It doesn't fix slow lists or JS-thread animations. Combine it with FlashList v2 for recycling, Reanimated 4 for UI-thread animation, and Zustand selectors for focused subscriptions. Our components are written as plain, rules-of-React-friendly code, so they work with the compiler enabled.