React Memo: A Beginner’s Guide
24 April 2025 | Category: React Js
Welcome to this beginner-friendly tutorial on React Memo! React.memo
is a higher-order component in React that optimizes performance by preventing unnecessary re-renders of components when their props haven’t changed. In this guide, you’ll learn what React.memo
is, how it works, when to use it, and how to combine it with other React features. We’ll apply these concepts by building a Todo List App with a list of todos and a filter, demonstrating how React.memo
improves performance. By the end, you’ll be confident using React.memo
to optimize your React applications for better performance.
What is React Memo?
React.memo
is a higher-order component (HOC) that memoizes a functional component, ensuring it only re-renders when its props change. By default, React re-renders a component whenever its parent re-renders or its state/props change, even if the props are unchanged. React.memo
performs a shallow comparison of props to skip re-renders, improving performance for components with expensive rendering logic or frequent parent updates.
Why Learn React Memo?
- Performance Optimization: Reduce unnecessary re-renders to make apps faster, especially for large lists or complex components.
- Scalability: Ensure apps remain performant as they grow with more components and state updates.
- User Experience: Prevent UI lag in apps with frequent updates, like filters or real-time data.
- Core Skill: Understanding memoization is key for building efficient React applications.
Prerequisites
Before starting, you should have:
- Basic knowledge of React (components, props, state, JSX, events, conditional rendering, lists, forms) and JavaScript (ES6).
- Node.js and npm installed (download from nodejs.org).
- A code editor like Visual Studio Code.
- A terminal for running commands.
We’ll use Create React App to set up our project and Tailwind CSS for responsive, attractive styling, consistent with your preference for visually appealing designs. We’ll also use React.memo
from React’s core library (no additional installation needed).
Key React Memo Concepts
Let’s explore the core concepts for using React.memo
.
1. Basic Usage of React.memo
Wrap a functional component with React.memo
to memoize it. The component only re-renders if its props change (via shallow comparison).
Example:
import React from 'react';
const Item = ({ name }) => {
console.log('Item rendered');
return <div>{name}</div>;
};
export default React.memo(Item);
2. When to Use React.memo
Use React.memo
for:
- Components that render frequently due to parent re-renders.
- Components with expensive rendering logic (e.g., complex calculations or large lists).
- Components with stable props that rarely change.
Example:
const ExpensiveComponent = ({ data }) => {
// Complex rendering logic
return <div>{data}</div>;
};
export default React.memo(ExpensiveComponent);
3. Shallow Prop Comparison
React.memo
compares props shallowly, meaning it checks if primitive props (e.g., strings, numbers) are identical and if object/array props have the same reference. If props are objects, ensure they don’t change unnecessarily.
Example:
// Bad: Creates a new object on every render
const Parent = () => {
const obj = { value: 1 };
return <Child data={obj} />;
};
// Good: Stable object reference
const obj = { value: 1 };
const Parent = () => {
return <Child data={obj} />;
};
const Child = React.memo(({ data }) => <div>{data.value}</div>);
4. Combining with useCallback
and useMemo
When passing functions or computed values as props, use useCallback
or useMemo
to prevent new references on each render, ensuring React.memo
works effectively.
Example:
const Parent = () => {
const [count, setCount] = React.useState(0);
const handleClick = React.useCallback(() => {
console.log('Clicked');
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<Child onClick={handleClick} />
</div>
);
};
const Child = React.memo(({ onClick }) => {
console.log('Child rendered');
return <button onClick={onClick}>Click Me</button>;
});
5. When Not to Use React.memo
Avoid React.memo
for:
- Components that re-render frequently due to changing props or state.
- Simple components with minimal rendering cost (e.g., a single
<div>
). - Over-optimization, as it adds memory overhead and complexity.
Example:
// Unnecessary: Simple component with frequent updates
const Simple = ({ value }) => <span>{value}</span>;
export default React.memo(Simple); // Likely overkill
Setting Up the Project
Let’s create a React app to build our Todo List App, which will use React.memo
to optimize rendering of todo items.
- Create a New React App:
Open your terminal and run:npx create-react-app todo-list cd todo-list
- Install Tailwind CSS:
Install and configure Tailwind CSS for responsive styling:npm install -D tailwindcss npx tailwindcss init
Updatetailwind.config.js
:/** @type {import('tailwindcss').Config} */ module.exports = { content: ['./src/**/*.{js,jsx,ts,tsx}'], theme: { extend: {} }, plugins: [] };
Updatesrc/index.css
:@tailwind base; @tailwind components; @tailwind utilities; body { font-family: Arial, sans-serif; margin: 0; background-color: #f5f5f5; }
- Start the Development Server:
npm start
This opens your app athttp://localhost:3000
. - Clean Up:
Opensrc/App.js
and replace its content with:const App = () => { return ( <div className="container mx-auto p-4"> <h1 className="text-3xl font-bold text-gray-800">Todo List</h1> </div> ); }; export default App;
Deletesrc/App.css
andsrc/logo.svg
.
Building the Todo List App
Our Todo List App will:
- Display a list of todos using a
TodoItem
component, optimized withReact.memo
to prevent unnecessary re-renders. - Allow users to add new todos via a
TodoForm
component. - Include a filter to show all, completed, or pending todos, triggering frequent parent re-renders to demonstrate
React.memo
’s benefits. - Use Tailwind CSS for a responsive, visually appealing design, aligning with your preference for attractive UI.
Step 1: Create the TodoItem Component
- In
src
, create a file namedTodoItem.js
:import React from 'react'; const TodoItem = ({ id, title, isCompleted, onToggle, onDelete }) => { console.log(`TodoItem ${id} rendered`); return ( <div className="flex items-center bg-white p-4 rounded-lg shadow-md mb-4 max-w-md"> <div className="flex-1"> <h3 className={`text-lg font-semibold ${ isCompleted ? 'text-gray-400 line-through' : 'text-gray-800' }`} > {title} </h3> <p className={isCompleted ? 'text-green-500' : 'text-yellow-500'}> {isCompleted ? 'Completed' : 'Pending'} </p> </div> <div className="flex space-x-2"> <button onClick={() => onToggle(id)} className={`px-3 py-1 rounded-md ${ isCompleted ? 'bg-yellow-500 hover:bg-yellow-600' : 'bg-green-500 hover:bg-green-600' } text-white`} > {isCompleted ? 'Undo' : 'Complete'} </button> <button onClick={() => onDelete(id)} className="px-3 py-1 bg-red-500 text-white rounded-md hover:bg-red-600" > Delete </button> </div> </div> ); }; export default React.memo(TodoItem);
- Memo Features:
- Wrapped with
React.memo
to prevent re-renders when props (id
,title
,isCompleted
,onToggle
,onDelete
) don’t change. - Logs renders to demonstrate memoization (check console during filter changes).
- Uses conditional rendering for completed/pending styles and button text.
- Styled with Tailwind CSS for a responsive, card-based layout.
- Wrapped with
- Memo Features:
Step 2: Create the TodoForm Component
- In
src
, create a file namedTodoForm.js
:const TodoForm = ({ onAddTodo }) => { const [title, setTitle] = React.useState(''); const handleSubmit = (e) => { e.preventDefault(); if (title.trim()) { onAddTodo({ title, isCompleted: false }); setTitle(''); } }; return ( <form onSubmit={handleSubmit} className="mb-6 max-w-md"> <div className="flex space-x-2"> <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Add a todo" className="flex-1 p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" /> <button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600" > Add </button> </div> </form> ); }; export default TodoForm;
- Memo Features:
- A controlled form for adding todos, passing data via
onAddTodo
. - Not memoized, as it manages its own state and re-renders appropriately.
- Styled with Tailwind CSS for a compact, responsive form.
- A controlled form for adding todos, passing data via
- Memo Features:
Step 3: Update the App Component
- Update
src/App.js
to manage todos, filters, and render the app:import TodoForm from './TodoForm'; import TodoItem from './TodoItem'; const App = () => { const [todos, setTodos] = React.useState([ { id: '1', title: 'Learn React Memo', isCompleted: true }, { id: '2', title: 'Build Todo App', isCompleted: false } ]); const [filter, setFilter] = React.useState('all'); const addTodo = (newTodo) => { setTodos([...todos, { ...newTodo, id: Date.now().toString() }]); }; const toggleTodo = React.useCallback((id) => { setTodos((prev) => prev.map((todo) => todo.id === id ? { ...todo, isCompleted: !todo.isCompleted } : todo ) ); }, []); const deleteTodo = React.useCallback((id) => { setTodos((prev) => prev.filter((todo) => todo.id !== id)); }, []); const filteredTodos = todos.filter((todo) => { if (filter === 'completed') return todo.isCompleted; if (filter === 'pending') return !todo.isCompleted; return true; }); return ( <div className="container mx-auto p-4"> <h1 className="text-3xl font-bold text-gray-800 mb-6">Todo List</h1> <TodoForm onAddTodo={addTodo} /> <div className="flex space-x-2 mb-4"> <button onClick={() => setFilter('all')} className={`px-4 py-2 rounded-md ${ filter === 'all' ? 'bg-blue-500 text-white' : 'bg-gray-200' }`} > All </button> <button onClick={() => setFilter('completed')} className={`px-4 py-2 rounded-md ${ filter === 'completed' ? 'bg-blue-500 text-white' : 'bg-gray-200' }`} > Completed </button> <button onClick={() => setFilter('pending')} className={`px-4 py-2 rounded-md ${ filter === 'pending' ? 'bg-blue-500 text-white' : 'bg-gray-200' }`} > Pending </button> </div> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> {filteredTodos.length > 0 ? ( filteredTodos.map((todo) => ( <TodoItem key={todo.id} id={todo.id} title={todo.title} isCompleted={todo.isCompleted} onToggle={toggleTodo} onDelete={deleteTodo} /> )) ) : ( <p className="text-gray-500">No todos match the selected filter.</p> )} </div> </div> ); }; export default App;
- Memo Features:
- Uses
React.memo
onTodoItem
to prevent re-renders whenfilter
changes but todo props remain the same. - Wraps
toggleTodo
anddeleteTodo
inuseCallback
to ensure stable function references, preventing unnecessaryTodoItem
re-renders. - Demonstrates frequent parent re-renders (via filter changes) to show
React.memo
’s benefits (check console logs). - Uses conditional rendering for empty states and list rendering with unique
key
props. - Styled with Tailwind CSS for a responsive grid layout.
- Uses
- Memo Features:
Step 4: Test the App
- Save all files and ensure the development server is running (
npm start
). - Open
http://localhost:3000
and open the browser console (F12 → Console). You should see:- Two initial todos (“Learn React Memo” and “Build Todo App”) in a responsive grid.
- A form to add new todos.
- Filter buttons to show all, completed, or pending todos.
- Console logs for
TodoItem
renders (e.g., “TodoItem 1 rendered”).
- Try:
- Adding a new todo (e.g., “Test Memo”).
- Toggling a todo’s completion status or deleting it.
- Clicking filter buttons (All, Completed, Pending) and checking the console. Without
React.memo
, everyTodoItem
would re-render on filter changes; withReact.memo
, only items with changed props (or new items) render. - Verifying responsiveness by resizing the browser or using mobile view in dev tools.
Understanding the Code
Let’s recap how React.memo
powers our Todo List App:
- Memoization:
TodoItem
is wrapped withReact.memo
, preventing re-renders when props (id
,title
,isCompleted
,onToggle
,onDelete
) don’t change, even when the parent (App
) re-renders due tofilter
updates. - Stable Props:
useCallback
ensurestoggleTodo
anddeleteTodo
maintain the same reference, allowingReact.memo
to skip re-renders effectively. - Performance Demonstration: Filter changes cause
App
to re-render, butTodoItem
components only log renders when their props change (e.g., on toggle or delete), showingReact.memo
’s optimization. - Responsive Design: Tailwind CSS ensures a mobile-friendly grid layout with clean buttons and cards, aligning with your preference for attractive, responsive UI.
- ES6 Features:
- Arrow functions for components and handlers.
- Destructuring props (
{ id, title, isCompleted, ... }
). - Spread operator for state updates (
[...todos, newTodo]
). - Modules for component organization.
Best Practices for React Memo
- Use Sparingly: Apply
React.memo
only to components with expensive renders or frequent parent re-renders:const ComplexComponent = React.memo(({ data }) => { ... });
- Ensure Stable Props: Use
useCallback
for function props anduseMemo
for object/array props to prevent unnecessary reference changes:const handleClick = React.useCallback(() => {}, []); const data = React.useMemo(() => ({ value: 1 }), []);
- Test Performance: Use browser dev tools (e.g., React Profiler) to confirm
React.memo
improves performance before adding it. - Combine with Other Optimizations: Pair with
useMemo
for computed values oruseCallback
for handlers:const computed = React.useMemo(() => expensiveCalculation(data), [data]);
- Avoid Overuse: Don’t memoize simple components or those with frequently changing props, as it adds overhead:
// Unnecessary const Simple = React.memo(({ value }) => <span>{value}</span>);
- Debug with Logs: Add
console.log
to components to verify re-renders and confirmReact.memo
’s effectiveness.
Common React Memo Pitfalls and Fixes
- Unstable Props:
Problem:React.memo
doesn’t prevent re-renders if props (e.g., functions, objects) change references unnecessarily.
Fix: UseuseCallback
oruseMemo
:const handleClick = React.useCallback(() => {}, []);
- Over-Memoization:
Problem: Memoizing every component adds complexity and memory overhead.
Fix: Only memoize components with measurable performance issues. - Deep Prop Changes:
Problem:React.memo
only does shallow comparison, so deep object changes may not trigger re-renders.
Fix: Use a custom comparison function withReact.memo
:const MyComponent = React.memo( ({ data }) => <div>{data.value}</div>, (prevProps, nextProps) => prevProps.data.value === nextProps.data.value );
- Ignoring State Changes:
Problem:React.memo
doesn’t prevent re-renders due to internal state changes.
Fix: Move state to a parent or child component if memoization is critical.
React Memo in Functional vs. Class Components
Since you’ve explored class components previously, here’s how memoization differs:
- Functional Components: Use
React.memo
to wrap the entire component:const Item = React.memo(({ name }) => <div>{name}</div>);
- Class Components:
React.memo
is for functional components only. For class components, usePureComponent
orshouldComponentUpdate
:class Item extends React.PureComponent { render() { return <div>{this.props.name}</div>; } }
Our app uses functional components with React.memo
for simplicity and alignment with modern React, but PureComponent
achieves similar optimization for classes.
What’s Next?
You’ve built a Todo List App using React.memo
! Here are some next steps:
- Add Features: Implement an “Edit Todo” feature, ensuring
React.memo
doesn’t interfere with state updates. - Learn More Optimizations: Explore
useMemo
for memoizing values or React’s Profiler for performance analysis. - Enhance Styling: Add animations with Tailwind CSS or Framer Motion for todo transitions.
- Build Another App: Create a dashboard or product list with memoized components to practice optimization.
Practice Challenge
Add a “Priority” field to todos (e.g., Low, Medium, High) and memoize a PriorityBadge
component that displays the priority with a color (e.g., red for High). Use useCallback
for any passed handlers and verify that PriorityBadge
only re-renders when priority changes.
Resources
- React Documentation: React.memo
- React Documentation: Optimizing Performance
- Tailwind CSS Documentation
- MDN: ES6
- Create React App Guide
Congratulations on mastering React.memo
! You’re now equipped to optimize your React applications for better performance. Keep practicing and happy coding!