Memoization is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again. In the context of React.js, memoization helps improve the performance of your application by preventing unnecessary re-renders of components. This chapter will guide you through the basics of memoization, delve into advanced topics, and provide comprehensive examples to ensure you have a solid understanding of this crucial concept.
Memoization is a technique that stores the results of function calls and reuses those results when the same inputs are provided. It’s like having a notepad where you jot down the results of complex calculations so you don’t have to repeat them.
function expensiveCalculation(num) {
console.log('Calculating...');
return num * 2;
}
const memo = {};
function memoizedCalculation(num) {
if (memo[num] !== undefined) {
return memo[num];
} else {
const result = expensiveCalculation(num);
memo[num] = result;
return result;
}
}
console.log(memoizedCalculation(5)); // Calculating... 10
console.log(memoizedCalculation(5)); // 10
expensiveCalculation
is a function that performs a costly calculation.memoizedCalculation
caches the results of expensiveCalculation
to avoid redundant calculations.In React, components can re-render frequently, especially in complex applications. Memoization helps in:
useMemo
is a hook that memoizes the result of a function. It only recalculates the result when one of its dependencies changes.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
import React, { useState, useMemo } from 'react';
function ExpensiveComponent({ num }) {
const computeExpensiveValue = (num) => {
console.log('Computing...');
return num * 2;
};
const memoizedValue = useMemo(() => computeExpensiveValue(num), [num]);
return (
Computed Value: {memoizedValue}
);
}
function App() {
const [count, setCount] = useState(0);
const [value, setValue] = useState(1);
return (
);
}
export default App;
ExpensiveComponent
does not re-render because its num
prop hasn’t changed.ExpensiveComponent
re-renders and recalculates the value.React.memo
is a higher-order component that memoizes the rendered output of a component. It helps prevent unnecessary renders when the props of the component haven’t changed.
const MemoizedComponent = React.memo(Component);
import React, { useState } from 'react';
const ExpensiveComponent = React.memo(({ num }) => {
console.log('Rendering...');
return Computed Value: {num * 2}
;
});
function App() {
const [count, setCount] = useState(0);
const [value, setValue] = useState(1);
return (
);
}
export default App;
ExpensiveComponent
does not re-render.ExpensiveComponent
re-renders.useCallback
memoizes a function, allowing React to retain the same function instance between renders unless one of its dependencies changes.
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
import React, { useState, useCallback } from 'react';
const ChildComponent = React.memo(({ onClick }) => {
console.log('Child Rendering...');
return ;
});
function App() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []);
return (
);
}
export default App;
ChildComponent
does not re-render when “Increment Count” is clicked because handleClick
is memoized.Creating custom hooks can encapsulate memoization logic, making your components cleaner and more reusable.
import React, { useState, useMemo } from 'react';
function useExpensiveCalculation(num) {
return useMemo(() => {
console.log('Computing expensive calculation...');
return num * 2;
}, [num]);
}
function App() {
const [value, setValue] = useState(1);
const computedValue = useExpensiveCalculation(value);
return (
Computed Value: {computedValue}
);
}
export default App;
When memoizing objects or arrays, ensure deep equality checks to avoid unnecessary recalculations.
import React, { useState, useMemo } from 'react';
function App() {
const [value, setValue] = useState({ num: 1 });
const memoizedValue = useMemo(() => {
console.log('Computing...');
return value.num * 2;
}, [value.num]);
return (
Computed Value: {memoizedValue}
);
}
export default App;
value
object changes.Memoization can improve performance but also adds complexity. Use it judiciously and benchmark your application to ensure that memoization provides a net performance gain.
useMemo
and useCallback
are correctly specified to avoid stale values.lodash.isequal
to compare dependencies accurately.useMemo
memoizes values, React.memo
memoizes components, and useCallback
memoizes functions.In this example, we’ll create a list of items where each item can be marked as “done”. We’ll use React.memo
to memoize the list items and useMemo
to filter the done items.
TodoList
.
import React, { useState, useMemo } from 'react';
// Memoized TodoItem component
const TodoItem = React.memo(({ todo, toggleDone }) => {
console.log('Rendering TodoItem:', todo.text);
return (
toggleDone(todo.id)}
/>
{todo.text}
);
});
function TodoList({ todos, toggleDone }) {
// Memoize the filtered list of done items
const doneTodos = useMemo(() => {
console.log('Filtering done todos...');
return todos.filter(todo => todo.done);
}, [todos]);
return (
Done Todos
{doneTodos.map(todo => (
))}
);
}
function App() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React', done: false },
{ id: 2, text: 'Learn Memoization', done: false },
{ id: 3, text: 'Build a React App', done: true },
]);
const toggleDone = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
};
return (
Todo List
{todos.map(todo => (
))}
);
}
export default App;
React.memo
to prevent re-renders unless its props change.useMemo
to filter the list of done todos, ensuring the filtering logic runs only when todos
changes.toggleDone
function to toggle the done status of a todo.TodoItem
components and the filtered list re-render.In this example, we’ll create a search component that filters a list of users based on the search input. We’ll use useCallback
to memoize the search handler function.
SearchInput
and UserList
.
import React, { useState, useCallback } from 'react';
// Memoized SearchInput component
const SearchInput = React.memo(({ query, setQuery }) => {
console.log('Rendering SearchInput');
return (
setQuery(e.target.value)}
placeholder="Search users..."
/>
);
});
function UserList({ users, query }) {
const filteredUsers = users.filter(user =>
user.name.toLowerCase().includes(query.toLowerCase())
);
return (
{filteredUsers.map(user => (
- {user.name}
))}
);
}
function App() {
const [query, setQuery] = useState('');
const [users] = useState([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Smith' },
{ id: 3, name: 'Jack Johnson' },
]);
// Memoize setQuery function using useCallback
const memoizedSetQuery = useCallback((newQuery) => {
setQuery(newQuery);
}, []);
return (
User Search
);
}
export default App;
React.memo
to prevent re-renders unless its props change.setQuery
function to SearchInput
using useCallback
.UserList
component filters the users based on the search query.Both examples demonstrate practical use cases for memoization in React applications:
React.memo
and useMemo
to optimize a todo list, preventing unnecessary re-renders.useCallback
to memoize the search handler function, improving performance by avoiding unnecessary re-renders of the search input component.Memoization is a powerful tool in React.js that helps optimize performance by caching results and preventing unnecessary re-renders. By understanding and implementing useMemo, useCallback, and React.memo, you can enhance the efficiency of your React applications. Remember to use memoization judiciously and always measure its impact on your application's performance.Happy coding !❤️