React Performance Basics
React performance optimization isn’t about making the code run faster; it’s about making React do less work. By default, React is very “cautious” — it re-renders more often than it strictly needs to, just to ensure the UI is always in sync with the state.
Re-renders
A component re-runs when:
- Its state changes
- Its props change
- Its parent re-renders
Example:
function Parent() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(count + 1)}>+</button>
<Child />
</>
);
}
/* When count changes
Parent re-renders
↓
Child ALSO re-renders Even if Child doesn’t use count.
*/
The Goal: Prevent “unnecessary” re-renders of expensive child components when the data they care about hasn’t actually changed.
React.memo
React.memo is a Higher-Order Component that wraps a component. It tells React: "Only re-render this component if its props have changed”, thus preventing unnecessary re-renders.
If the parent re-renders but the props passed to the child are identical, React will skip the child and use the last rendered output (memoization).
// Without memo - Child re-renders every time parent re-renders.
function Child() {
console.log("Child rendered");
return <p>I'm child</p>;
}
// With Memo - Child re-renders only when props (data) changes
const Child = React.memo(({ data }) => {
console.log("Rendering child...");
return <div>{data}</div>;
});When to use React.memo
- Expensive components
- Large lists
- Components that render often
useMemo
While React.memo stabilizes components, useMemo stabilizes values. Use it to cache the result of a complex calculation so it isn't recalculated on every render.
// without useMemo - calculates on every Render
const expensiveValue = heavyCalculation(data);
// with useMemo - Only recalculates when data changes
const expensiveValue = useMemo(() => {
return performHeavyCalculation(data);
}, [data]); // Only recalculates if 'data' changesWhen to use useMemo
- Expensive calculations
- Filtering large lists
- Derived data
useCallback
In JavaScript, every time a component re-renders, all functions defined inside it are re-created. To React, a “new” function looks like a change in props, which breaks React.memo.
Get Nimmikrishnab’s stories in your inbox
Join Medium for free to get updates from this writer.
useCallback caches the function instance itself.
// without useCallback - this arrow function is NEW on every render.
// so react thinks Props changed → Child re-renders even if logic is same.
<Child onClick={() => doSomething()} />
// with useCallbacks
// Now the function reference stays the same.
// Child won’t re-render unnecessarily.
const handleClick = useCallback(() => {
doSomething(id);
}, [id]); // Only changes if 'id' changes
useCallback caches the function until dependencies change.
How React.memo and useCallback Work Together
const Child = React.memo(({ onClick }) => {
console.log("Child rendered");
return <button onClick={onClick}>Click</button>;
});
function Parent() {
const handleClick = useCallback(() => {
console.log("Clicked");
}, []);
return <Child onClick={handleClick} />;
}
/*
Now:
• Parent re-renders
• Child does NOT re-render
• Because function reference stayed same
*/
Do NOT use these everywhere.
React is already fast.
Only optimize when:
- You measure performance issues
- You see unnecessary renders
- You work with big lists / heavy UI
Premature optimization = complexity.
Important “Hidden” Tips
A. Moving State Down
Before reaching for memo, try moving state to a smaller child component. If only a small part of the UI needs to change, don't keep that state in a high-level parent that forces the whole tree to re-render.
B. Passing Components as Children
If you pass a component as children, React is smart enough to know it doesn't need to re-render it when the wrapper's state changes.
// This is more performant than nesting them directly
<ParentWrapper>
<ExpensiveChild />
</ParentWrapper>The “Referential Equality” Trap
This is the most important concept in React performance. In JavaScript:
5 === 5is true.{}==={}is false.() => {}===() => {}is false.
Because objects and functions are compared by reference (where they sit in memory), React.memo will think the props changed every time unless you wrap those objects in useMemo and those functions in useCallback.
Summary
- Re-render = Component runs again
- React.memo → Protect components
- useMemo → Protect values
- useCallback → Protect functions








