Memoization

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.

What is Memoization?

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

				
			

Explanation

  • expensiveCalculation is a function that performs a costly calculation.
  • memoizedCalculation caches the results of expensiveCalculation to avoid redundant calculations.

Why is Memoization Important in React?

In React, components can re-render frequently, especially in complex applications. Memoization helps in:

  • Preventing Unnecessary Re-renders: By caching the result of a function, React can skip rendering a component if its props or state haven’t changed.
  • Improving Performance: Reducing the number of renders improves the performance and responsiveness of your application.

Basic Memoization with useMemo

useMemo is a hook that memoizes the result of a function. It only recalculates the result when one of its dependencies changes.

Syntax

				
					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 (
        <div>
            <p>Computed Value: {memoizedValue}</p>
        </div>
    );
}

function App() {
    const [count, setCount] = useState(0);
    const [value, setValue] = useState(1);

    return (
        <div>
            <button onClick={() => setCount(count + 1)}>Increment Count: {count}</button>
            <ExpensiveComponent num={value} />
            <button onClick={() => setValue(value + 1)}>Increment Value: {value}</button>
        </div>
    );
}

export default App;

				
			

Output

  • When you click “Increment Count,” the ExpensiveComponent does not re-render because its num prop hasn’t changed.
  • When you click “Increment Value,” ExpensiveComponent re-renders and recalculates the value.

Memoizing Functional Components with React.memo

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.

Syntax

				
					const MemoizedComponent = React.memo(Component);

				
			
				
					import React, { useState } from 'react';

const ExpensiveComponent = React.memo(({ num }) => {
    console.log('Rendering...');
    return <p>Computed Value: {num * 2}</p>;
});

function App() {
    const [count, setCount] = useState(0);
    const [value, setValue] = useState(1);

    return (
        <div>
            <button onClick={() => setCount(count + 1)}>Increment Count: {count}</button>
            <ExpensiveComponent num={value} />
            <button onClick={() => setValue(value + 1)}>Increment Value: {value}</button>
        </div>
    );
}

export default App;

				
			

Output

  • When “Increment Count” is clicked, ExpensiveComponent does not re-render.
  • When “Increment Value” is clicked, ExpensiveComponent re-renders.

Memoization with useCallback

useCallback memoizes a function, allowing React to retain the same function instance between renders unless one of its dependencies changes.

Syntax

				
					const memoizedCallback = useCallback(() => {
    doSomething(a, b);
}, [a, b]);

				
			
				
					import React, { useState, useCallback } from 'react';

const ChildComponent = React.memo(({ onClick }) => {
    console.log('Child Rendering...');
    return <button onClick={onClick}>Click Me</button>;
});

function App() {
    const [count, setCount] = useState(0);

    const handleClick = useCallback(() => {
        console.log('Button clicked');
    }, []);

    return (
        <div>
            <button onClick={() => setCount(count + 1)}>Increment Count: {count}</button>
            <ChildComponent onClick={handleClick} />
        </div>
    );
}

export default App;

				
			

Output

  • ChildComponent does not re-render when “Increment Count” is clicked because handleClick is memoized.

Advanced Memoization Techniques

Custom Hooks for Memoization

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 (
        <div>
            <p>Computed Value: {computedValue}</p>
            <button onClick={() => setValue(value + 1)}>Increment Value: {value}</button>
        </div>
    );
}

export default App;

				
			

Memoizing Complex Data Structures

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 (
        <div>
            <p>Computed Value: {memoizedValue}</p>
            <button onClick={() => setValue({ num: value.num + 1 })}>Increment Value: {value.num}</button>
        </div>
    );
}

export default App;

				
			

Output

  • When the button is clicked, the component re-renders and recalculates the value because the reference to value object changes.

Performance Considerations

Memoization can improve performance but also adds complexity. Use it judiciously and benchmark your application to ensure that memoization provides a net performance gain.

Common Pitfalls and Best Practices

  • Overuse: Not all components benefit from memoization. Use it for components with expensive calculations or frequent re-renders.
  • Dependency Management: Ensure dependencies in useMemo and useCallback are correctly specified to avoid stale values.
  • Deep Equality Checks: For complex objects, use libraries like lodash.isequal to compare dependencies accurately.

Summary

  • Memoization stores the results of function calls and returns cached results for the same inputs.
  • In React, memoization prevents unnecessary re-renders and improves performance.
  • useMemo memoizes values, React.memo memoizes components, and useCallback memoizes functions.
  • Custom hooks can encapsulate memoization logic for cleaner code.
  • Ensure proper dependency management and avoid overuse to gain optimal benefits from memoization.

Example 1: Optimizing a List Component with React.memo and useMemo

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.

Components

  1. TodoItem: A memoized component that displays a single todo item.
  2. TodoList: A component that filters and displays the list of todo items.
  3. App: The main component that manages the state and renders TodoList.
				
					import React, { useState, useMemo } from 'react';

// Memoized TodoItem component
const TodoItem = React.memo(({ todo, toggleDone }) => {
    console.log('Rendering TodoItem:', todo.text);
    return (
        <li>
            <input
                type="checkbox"
                checked={todo.done}
                onChange={() => toggleDone(todo.id)}
            />
            {todo.text}
        </li>
    );
});

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 (
        <div>
            <h3>Done Todos</h3>
            <ul>
                {doneTodos.map(todo => (
                    <TodoItem key={todo.id} todo={todo} toggleDone={toggleDone} />
                ))}
            </ul>
        </div>
    );
}

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 (
        <div>
            <h2>Todo List</h2>
            <ul>
                {todos.map(todo => (
                    <TodoItem key={todo.id} todo={todo} toggleDone={toggleDone} />
                ))}
            </ul>
            <TodoList todos={todos} toggleDone={toggleDone} />
        </div>
    );
}

export default App;

				
			

Explanation

  • TodoItem: This component is memoized with React.memo to prevent re-renders unless its props change.
  • TodoList: This component uses useMemo to filter the list of done todos, ensuring the filtering logic runs only when todos changes.
  • App: The main component manages the list of todos and provides the toggleDone function to toggle the done status of a todo.

Output

  • Initially, “Learn React” and “Learn Memoization” are not done, while “Build a React App” is done.
  • When a todo item is toggled, only the relevant TodoItem components and the filtered list re-render.

Example 2: Optimizing a Search Component with useCallback

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.

Components

  1. SearchInput: A memoized component that renders an input field for searching.
  2. UserList: A component that displays the list of users based on the search query.
  3. App: The main component that manages the state and renders SearchInput and UserList.
				
					import React, { useState, useCallback } from 'react';

// Memoized SearchInput component
const SearchInput = React.memo(({ query, setQuery }) => {
    console.log('Rendering SearchInput');
    return (
        <input
            type="text"
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            placeholder="Search users..."
        />
    );
});

function UserList({ users, query }) {
    const filteredUsers = users.filter(user =>
        user.name.toLowerCase().includes(query.toLowerCase())
    );

    return (
        <ul>
            {filteredUsers.map(user => (
                <li key={user.id}>{user.name}</li>
            ))}
        </ul>
    );
}

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 (
        <div>
            <h2>User Search</h2>
            <SearchInput query={query} setQuery={memoizedSetQuery} />
            <UserList users={users} query={query} />
        </div>
    );
}

export default App;

				
			

Explanation

  • SearchInput: This component is memoized with React.memo to prevent re-renders unless its props change.
  • UserList: This component filters and displays the list of users based on the search query.
  • App: The main component manages the search query state and passes a memoized setQuery function to SearchInput using useCallback.

Output

  • Initially, all users are displayed.
  • As the user types in the search input, the UserList component filters the users based on the search query.

Both examples demonstrate practical use cases for memoization in React applications:

  • The first example uses React.memo and useMemo to optimize a todo list, preventing unnecessary re-renders.
  • The second example uses 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 !❤️

Table of Contents

Contact here

Copyright © 2025 Diginode

Made with ❤️ in India