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 !❤️
