Introduction
If you’ve studied Data Structures & Algorithms, you've probably used memoization, a technique that stores the results of recursive function calls and returns the cached result when the same inputs occur again.
Now, imagine that same idea applied to your React UI: What if we could skip re-running heavy logic or re-rendering parts of the screen unless something actually changed?
Sometimes your components work perfectly, but something still feels off: your app feels laggy, or you notice parts of the UI re-rendering even when they don’t need to.
That’s where memo and useMemo come in.
What is a memo?
React.memo is a higher-order component that tells React: Only to re-render a component if its props actually changed. It’s used to optimize functional components by preventing unnecessary re-renders.
What about useMemo?
useMemo is a hook that memoizes (remembers) the result of a function and only recalculates it when its dependencies change.
It’s great for skipping expensive calculations when props or states haven't changed.
When do you use them?
- React.memo: Your child component is re-rendering unnecessarily
- useMemo: You’re doing a heavy calculation on every render
What are we trying to do here?
Let's have a counter button to increment a number and an input field to update a name. We'll demonstrate how either incrementing an count or changing a name causes child components to re-render and cause expensive operations to re-run and then we'll see how memoization can prevent that.
File structure
memo-demo/
├── src/
│ ├── components/
│ │ ├── Child.tsx # Child component (memoized)
│ │ ├── Memo.tsx # Optimized parent using useMemo & React.memo
│ │
│ ├── types/
│ │ └── SharedTypes.ts # SharedProps interface for reusable prop types
│ │
│ ├── App.tsx # Main app entry that switches between WithMemo & WithoutMemo
Let's start with SharedTypes.ts
- count: A number that we increment using a button.
- name: A string we update using an input.
- setCount: Function to update count, comes from useState.
- setName: Function to update name, also from useState.
export interface SharedProps {
count: number;
name: string;
setCount: React.Dispatch<React.SetStateAction<number>>;
setName: React.Dispatch<React.SetStateAction<string>>;
}
Memo.tsx
I am deliberately avoiding optimization, so the expensive function updateNumber() and the Child component both re-run every time the count or name changes.
- updateNumber(num): A function to logs to the console and loops 10 million times to simulate heavy computation when counting changes.
- Component Props
- count, name: state values
- setCount, setName: state setters passed from the parent (App.tsx)
- What it Renders
- A heading, current count, and result from the expensive updateNumber() call.
- A button to increment the count.
- A text input to update the name.
- A <Child /> component that takes a name as a prop.
import { SharedProps } from '../types/SharedTypes';
import { Child } from './Child';
import './WithoutMemo.css';
function heavyOperation(): number {
console.log('heavyOperation gets called');
let result = 0;
for (let i = 0; i < 1e7; i++) {
result += i;
}
return result;
}
export function WithoutMemo({ count, name, setCount, setName }: SharedProps) {
const newNumber = heavyOperation();
return (
<div className="container">
<h2>Without Memoization</h2>
<h4>Count: {count}</h4>
<button onClick={() => setCount(prev => prev + 1)}>Increment</button>
<input value={name} onChange={e => setName(e.target.value)} />
<p>Value: {newNumber}</p>
<Child name={name} />
</div>
);
}
The Child Component
This is a child component that displays the name prop.
- ChildProps interface: Defines that this component expects a single prop, name (a string).
- Child component
- Logs to the console only when the name prop changes.
- Renders a <h3> with the current name.
interface ChildProps {
name: string;
}
export const Child = function Child({ name }: ChildProps) {
console.log('name chagned in child component');
return <h3>Child Component: {name}</h3>;
};
Output
The counter has no direct relation to the heavyOperation() function or the Child-component. However, as you can see from the following output when I increment the counter or change the name, the heavyOperation() function gets called, and my child component re-renders.
![Memo]()
useMemo
In our Memo-component, typing in the name input triggers a re-render. We saw how heavyOperation() would run every time, even when count didn’t change. Using useMemo ensures the heavyOperation() only happens when needed.
- useMemo is a React hook that memoizes (remembers) the result of a function, here, heavyOperation().
- The function inside useMemo (i.e., heavyOperation()) runs only when the dependency array changes.
- Here, the dependency array is [count], so
- heavyOperation() will run only when count changes.
- If the count stays the same between renders, React returns the previously cached value and skips running heavyOperation() again.
- This avoids expensive recalculations on every render and improves performance.
import { useMemo } from 'react';
...
export function WithoutMemo({ count, name, setCount, setName }: SharedProps) {
const newNumber = useMemo(() => heavyOperation(), [count]);
....
....
}
Output: As you can see, heavyOperation() is not being called on the name change.
![Reactmemo]()
React.Memo
We have a Child component like this, Every time you click the increment-button to change count, the parent re-renders. Since Child is a regular component, it also re-renders even though name hasn’t changed.
interface ChildProps {
name: string;
}
export const Child = function Child({ name }: ChildProps) {
console.log('name chagned in child component');
return <h3>Child Component: {name}</h3>;
};
Here, we can use React.memo. It wraps the Child component and tells React: “Only re-render this component if its props have changed.”
With React.memo, the Child component will not re-render unless name prop changes, even if the parent re-renders for other reasons, like count changing.
Modify the child component like this.
import React from 'react';
...
export const Child = React.memo(function Child({ name }: ChildProps) {
....
....
});
With React.memo
- Now, React skips re-rendering the Child component when only the count changes.
- The child re-renders only when the actual name prop changes.
Output: Now things are in perfect balance
![Usereact]()
Conclusion
Using React.memo and useMemo doesn’t make your app faster by default but they’re powerful tools when you start hitting real performance costs. Use them wisely when
- Your components are re-rendering too often.
- You have heavy computations inside a component.
- You want to avoid child updates unless props truly change.