Differrence between useMemo and useCallback

In React, useMemo and useCallback are Hooks that help optimize the performance by memoizing values. They both accept a function as an argument and return a memoized version of the function.

The main difference is while useMemo returns a memoized value that is the result of running the function, useCallback returns a memoized callback function that only changes if one of the dependencies has changed.

useMemo

React useMemo is a hook that lets you cache the result of a calculation between re-renders. It is useful when you want to avoid expensive calculations on every render, by caching the previous result.

const cachedValue = useMemo(calculateValue, dependencies)

To use useMemo, you need to import it from React, and then call it with a function that returns the value you want to memoize, and an array of dependencies that determine when to re-run the function.

import { useState, useMemo } from "react";

const ExpensiveCalculation = ({ number }) => {
  const calculateFactorial = (n) => {
    if (n < 0) {
      return -1;
    }
    if (n === 0 || n === 1) {
      return 1;
    }
    return n * calculateFactorial(n - 1);
  };

  const factorial = useMemo(() => {
    console.log("Calculating factorial");
    return calculateFactorial(number);
  }, [number]);

  return (
    <p>
      {number}! = {factorial}
    </p>
  );
};

const App = () => {
  const [number, setNumber] = useState(0);

  const handleChange = (e) => {
    setNumber(e.target.value);
  };

  return (
    <div>
      <h1>Expensive Calculation App</h1>
      <input type="number" value={number} onChange={handleChange} />
      <ExpensiveCalculation number={number} />
    </div>
  );
};

In this example, the App component has a number state that controls the input value and the prop passed to the ExpensiveCalculation component. The ExpensiveCalculation component has a function that calculates the factorial of a given number, which is a potentially expensive operation.

The ExpensiveCalculation component uses useMemo to memoize the result of the calculation, so it only runs when the number prop changes. This prevents unnecessary re-calculation when the ExpensiveCalculation component re-renders for other reasons. The ExpensiveCalculation component also renders the result of the calculation in a paragraph.

useCallback

React useCallback is a hook that lets you cache a function definition between re-renders. This can improve performance by preventing unnecessary re-rendering of components that depend on the function.

const cachedFn = useCallback(fn, dependencies)

To use useCallback, you need to pass two parameters: the function you want to cache, and an array of dependencies that determine when the function should be updated.

import { useState, useEffect, useCallback } from "react";

const Timer = ({ start, stop }) => {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    let interval = null;
    if (start) {
      interval = setInterval(() => {
        setSeconds((s) => s + 1);
      }, 1000);
    } else if (!start && seconds !== 0) {
      clearInterval(interval);
    }
    return () => clearInterval(interval);
  }, [start, seconds]);

  return (
    <div>
      <p>Seconds: {seconds}</p>
      <button onClick={stop}>Stop</button>
    </div>
  );
};

const App = () => {
  const [start, setStart] = useState(false);

  const handleStart = useCallback(() => {
    setStart(true);
  }, []);

  const handleStop = useCallback(() => {
    setStart(false);
  }, []);

  return (
    <div>
      <h1>Timer App</h1>
      <button onClick={handleStart}>Start</button>
      <Timer start={start} stop={handleStop} />
    </div>
  );
};

In this example, Timer component uses useEffect to set up and clear an interval that updates the seconds state every second. It also renders a button that calls the stop function prop to stop the timer. The App component has a start state that controls whether the Timer component should run or not.

The App component also passes two functions as props to the Timer component: handleStart and handleStop. These functions are memoized using useCallback, so they don’t change on every render of the App component. This prevents the Timer component from re-rendering unnecessarily when the start state changes.

Memoization

Memoization is an optimization technique that can enhance React’s performance by optimizing the rendering process. React provides several built-in mechanisms for memoization, which can enhance performance by caching the results and returning the cached result when the same inputs occur again.

In the context of a React app, memoization is a technique where, whenever the parent component re-renders, the child component re-renders only if there’s a change in the props. This can prevent unnecessary re-rendering of components that depend on props or state values that don’t change frequently. React also provides hooks for memoizing functions and values, such as useCallback and useMemo, which can avoid re-creating or re-calculating them on every render.

Some of the benefits of using memoization in React are:

  • Improving performance by reducing the number of DOM operations and avoiding expensive computations.
  • Preserving referential equality by returning the same object or function instance across renders.
  • Avoiding side effects or bugs caused by re-rendering or re-executing functions that depend on external factors.

Some of the drawbacks or challenges of using memoization in React are:

  • Adding complexity and overhead to the code by introducing extra hooks or components.
  • Choosing the right dependencies for memoization to ensure correctness and avoid stale values.
  • Managing cache invalidation and memory usage to avoid memory leaks or outdated results.

Referential equality

Referential equality in React is a concept that affects how often the components in your application re-render. React re-renders a component only when a change occurs to its state variables or props. To determine if a change has occurred, React compares the new value against the old value for equality using the Object.is() comparison algorithm.

Object.is() compares two values for referential equality if they are objects, meaning they point to the same object instance in memory. For example:

const obj1 = { name: "Alice" };
const obj2 = { name: "Alice" };
const obj3 = obj1;

console.log(Object.is(obj1, obj2)); // false
console.log(Object.is(obj1, obj3)); // true

In this example, obj1 and obj2 have the same properties and values, but they are different object instances, so they are not referentially equal. obj3 is assigned the same reference as obj1, so they are referentially equal.

Referential equality is important to understand when working with objects in React, especially when using hooks like useEffect, useCallback, or useMemo, which rely on referential equality to determine if an effect or a memoized value should be updated. If you pass an object as a dependency to these hooks, you need to make sure that the object reference does not change on every render, otherwise you might cause unnecessary re-rendering or re-executing of your effects or memoized values.