We use cookies to enhance your experience on the site
CodeWorlds

Memoization - React.memo, useMemo, and useCallback

In outer space, resources are limited and must be used wisely. Similarly in the React world, application performance is of key importance. Memoization is an optimization technique that allows "remembering" results of expensive operations and reusing them when input data hasn't changed - similar to saving and reusing trajectory calculations instead of performing them from scratch.

What is memoization in React?

Memoization in React is a performance optimization mechanism that allows you to:

  • Avoid unnecessary component re-renders
  • Prevent re-execution of expensive calculations
  • Stabilize references to functions and objects

React.memo - component memoization

`React.memo` is a higher-order component (HOC) that wraps a component and prevents it from re-rendering if its props haven't changed. zmianie.

Basic usage of React.memo

1import React from 'react';
2
3// Component without memoization
4function PlanetInfo({ name, diameter, distance }) {
5  console.log(\`Rendering planet info: \${name}\`);
6  return (
7    <div className="planet-info">
8      <h2>{name}</h2>
9      <p>Diameter: {diameter} km</p>
10      <p>Distance from Sun: {distance} million km</p>
11    </div>
12  );
13}
14
15// Same component with memoization
16const MemoizedPlanetInfo = React.memo(PlanetInfo);
17
18// Parent component
19function SolarSystem() {
20  const [count, setCount] = useState(0);
21
22  return (
23    <div>
24      <button onClick={() => setCount(count + 1)}>
25        Counter: {count}
26      </button>
27
28      {/* This component will only re-render when its props change */}
29      <MemoizedPlanetInfo
30        name="Mars"
31        diameter={6779}
32        distance={228}
33      />
34    </div>
35  );
36}

Notice that in the above example, even though the parent component `SolarSystem` renders again on every button click, the component `MemoizedPlanetInfo` will not re-render because its props remain unchanged.

Custom props comparison

By default `React.memo` compares props using shallow comparison. However, you can provide your own comparison function:

1const MemoizedPlanetInfo = React.memo(
2  PlanetInfo,
3  (prevProps, nextProps) => {
4    // Return true if you want to prevent re-rendering
5    // Return false if you want to re-render the component
6    return prevProps.name === nextProps.name &&
7           prevProps.diameter === nextProps.diameter;
8    // Note that we ignore changes to distance - the component won't re-render
9    // when only this property changes
10  }
11);

useMemo - value memoization

Hook `useMemo` allows remembering the result of an expensive operation and reusing it as long as dependencies haven't changed.

When to use useMemo?

  1. For expensive calculations
  2. When creating reference objects as props for child components
  3. When creating large data structures
1import React, { useState, useMemo } from 'react';
2
3function AsteroidCalculator({ asteroids }) {
4  const [filter, setFilter] = useState('all');
5
6  // Expensive calculation - memoized for better performance
7  const dangerousAsteroids = useMemo(() => {
8    console.log('Calculating dangerous asteroids...');
9    return asteroids.filter(asteroid => {
10      // Simulation of expensive calculations
11      let isDangerous = false;
12      for (let i = 0; i < 10000; i++) {
13        if (asteroid.velocity > 20 && asteroid.size > 1000 && asteroid.distance < 50000000) {
14          isDangerous = true;
15        }
16      }
17      return isDangerous;
18    });
19  }, [asteroids]); // Calculation will be re-executed only when the asteroids array changes
20
21  // This calculation is also memoized but depends on the filter
22  const displayedAsteroids = useMemo(() => {
23    console.log('Filtering asteroids...');
24    if (filter === 'all') return asteroids;
25    if (filter === 'dangerous') return dangerousAsteroids;
26    return [];
27  }, [filter, asteroids, dangerousAsteroids]);
28
29  return (
30    <div>
31      <div>
32        <button onClick={() => setFilter('all')}>All</button>
33        <button onClick={() => setFilter('dangerous')}>Nobezpieczne</button>
34      </div>
35      <h2>Asteroids ({displayedAsteroids.length}):</h2>
36      <ul>
37        {displayedAsteroids.map(asteroid => (
38          <li key={asteroid.id}>{asteroid.name}</li>
39        ))}
40      </ul>
41    </div>
42  );
43}

useCallback - function memoization

Hook `useCallback` is similar to `useMemo`, but instead of remembering the function's result, it remembers the function itself. It's particularly useful when passing functions as props to memoized child components.

1import React, { useState, useCallback } from 'react';
2
3function MissionControl() {
4  const [missionStatus, setMissionStatus] = useState('preparing');
5  const [fuelLevel, setFuelLevel] = useState(100);
6
7  // Z useCallback, function is recreated only when fuelLevel changes
8  const startMission = useCallback(() => {
9    if (fuelLevel > 20) {
10      setMissionStatus('in-progress');
11      console.log(\`Mission started with fuel level: \${fuelLevel}%\`);
12    } else {
13      console.log('Not enough fuel to start mission');
14    }
15  }, [fuelLevel]); // Dependency: function recreated when fuelLevel changes
16
17  const abortMission = useCallback(() => {
18    setMissionStatus('aborted');
19    console.log('Mission aborted!');
20  }, []); // Empty dependency array - function created only once
21
22  return (
23    <div>
24      <h2>Mission status: {missionStatus}</h2>
25      <p>Fuel level: {fuelLevel}%</p>
26
27      <button onClick={() => setFuelLevel(prev => Math.max(0, prev - 10))}>
28        Use fuel
29      </button>
30
31      {/* Passing memoized functions as props */}
32      <MemoizedControlPanel
33        onStart={startMission}
34        onAbort={abortMission}
35        fuelLevel={fuelLevel}
36      />
37    </div>
38  );
39}
40
41// Memoized child component that receives functions as props
42const ControlPanel = React.memo(({ onStart, onAbort, fuelLevel }) => {
43  console.log('Rendering control panel');
44
45  return (
46    <div className="control-panel">
47      <h3>Control panel</h3>
48      <button onClick={onStart} disabled={fuelLevel <= 20}>
49        Start
50      </button>
51      <button onClick={onAbort}>
52        Abort mission
53      </button>
54    </div>
55  );
56});
57
58const MemoizedControlPanel = React.memo(ControlPanel);

Comparing memoization techniques and when to use them

| Technique | Use case | When to use | |----------|--------------|--------------| | React.memo | Component | When a component frequently receives the same props | | useMemo | Value | For expensive calculations and complex data structures | | useCallback | Function | When passing functions as props to memoized components |

Common mistakes and pitfalls

1. Over-memoization

1// Not optimal - too simple a value to memoize
2const count = useMemo(() => 1 + 2, []);
3
4// Not optimal - simple function that doesn't need memoization
5const handleClick = useCallback(() => {
6  console.log('Clicked!');
7}, []);

2. Forgetting dependencies

1// Not safe - missing dependency 'value'
2const processedData = useMemo(() => {
3  return heavyProcessing(value);
4}, []); // Should be [value]

3. Too many dependencies

1// May reduce memoization effectiveness
2const result = useMemo(() => {
3  return computeResult(a, b);
4}, [a, b, c, d, e]); // Too many dependencies - memoization may be ineffective

Use memoization wisely

  1. Don't optimize prematurely - use profilers first to identify actual performance issues
  2. Apply memoization selectively - only where it's actually needed
  3. Remember the cost - memoization also has its cost, which may outweigh the gains for simpler operations

Summary

Memoization in React is a powerful optimization technique that - like conserving resources on a spaceship - helps efficiently manage application performance. Use it wisely:

  • Use `React.memo` for components that frequently re-render with the same props
  • Use `useMemo` for expensive calculations and creating complex data structures
  • Use `useCallback` to stabilize function references passed down the component tree

However, remember that like any optimization technique, memoization has its cost and should be applied judiciously, where it actually brings performance benefits.

Go to CodeWorlds