React  

One Cool Trick in React: Memo

Introduction

If you’ve studied Data Structures & Algorithms, you've probably used memoization, a technique that stores the results of recursive function calls and returns the cached result when the same inputs occur again.

Now, imagine that same idea applied to your React UI: What if we could skip re-running heavy logic or re-rendering parts of the screen unless something actually changed?

Sometimes your components work perfectly, but something still feels off: your app feels laggy, or you notice parts of the UI re-rendering even when they don’t need to.

That’s where memo and useMemo come in.

What is a memo?

React.memo is a higher-order component that tells React: Only to re-render a component if its props actually changed. It’s used to optimize functional components by preventing unnecessary re-renders.

What about useMemo?

useMemo is a hook that memoizes (remembers) the result of a function and only recalculates it when its dependencies change.

It’s great for skipping expensive calculations when props or states haven't changed.

When do you use them?

  • React.memo: Your child component is re-rendering unnecessarily
  • useMemo: You’re doing a heavy calculation on every render

What are we trying to do here?

Let's have a counter button to increment a number and an input field to update a name. We'll demonstrate how either incrementing an count or changing a name causes child components to re-render and cause expensive operations to re-run and then we'll see how memoization can prevent that.

File structure

memo-demo/
├── src/
│   ├── components/
│   │   ├── Child.tsx           # Child component (memoized)
│   │   ├── Memo.tsx            # Optimized parent using useMemo & React.memo
│   │
│   ├── types/
│   │   └── SharedTypes.ts      # SharedProps interface for reusable prop types
│   │
│   ├── App.tsx                 # Main app entry that switches between WithMemo & WithoutMemo

Let's start with SharedTypes.ts

  • count: A number that we increment using a button.
  • name: A string we update using an input.
  • setCount: Function to update count, comes from useState.
  • setName: Function to update name, also from useState.
export interface SharedProps {
  count: number;
  name: string;
  setCount: React.Dispatch<React.SetStateAction<number>>;
  setName: React.Dispatch<React.SetStateAction<string>>;
}

Memo.tsx

I am deliberately avoiding optimization, so the expensive function updateNumber() and the Child component both re-run every time the count or name changes.

  • updateNumber(num): A function to logs to the console and loops 10 million times to simulate heavy computation when counting changes.
  • Component Props
    • count, name: state values
    • setCount, setName: state setters passed from the parent (App.tsx)
  • What it Renders
    • A heading, current count, and result from the expensive updateNumber() call.
    • A button to increment the count.
    • A text input to update the name.
    • A <Child /> component that takes a name as a prop.
import { SharedProps } from '../types/SharedTypes';
import { Child } from './Child';
import './WithoutMemo.css';

function heavyOperation(): number {
  console.log('heavyOperation gets called');
  let result = 0;
  for (let i = 0; i < 1e7; i++) {
    result += i;
  }
  return result;
}

export function WithoutMemo({ count, name, setCount, setName }: SharedProps) {
  const newNumber = heavyOperation();

  return (
    <div className="container">
      <h2>Without Memoization</h2>
      <h4>Count: {count}</h4>
      <button onClick={() => setCount(prev => prev + 1)}>Increment</button>
      <input value={name} onChange={e => setName(e.target.value)} />
      <p>Value: {newNumber}</p>
      <Child name={name} />
    </div>
  );
}

The Child Component

This is a child component that displays the name prop.

  • ChildProps interface: Defines that this component expects a single prop, name (a string).
  • Child component
    • Logs to the console only when the name prop changes.
    • Renders a <h3> with the current name.
interface ChildProps {
  name: string;
}

export const Child = function Child({ name }: ChildProps) {
  console.log('name chagned in child component');
  return <h3>Child Component: {name}</h3>;
};

Output

The counter has no direct relation to the heavyOperation() function or the Child-component. However, as you can see from the following output when I increment the counter or change the name, the heavyOperation() function gets called, and my child component re-renders.

Memo

useMemo

In our Memo-component, typing in the name input triggers a re-render. We saw how heavyOperation() would run every time, even when count didn’t change. Using useMemo ensures the heavyOperation() only happens when needed.

  • useMemo is a React hook that memoizes (remembers) the result of a function, here, heavyOperation().
  • The function inside useMemo (i.e., heavyOperation()) runs only when the dependency array changes.
  • Here, the dependency array is [count], so
    • heavyOperation() will run only when count changes.
    • If the count stays the same between renders, React returns the previously cached value and skips running heavyOperation() again.
  • This avoids expensive recalculations on every render and improves performance.
import { useMemo } from 'react';
...

export function WithoutMemo({ count, name, setCount, setName }: SharedProps) {

    const newNumber = useMemo(() => heavyOperation(), [count]);
    ....
    ....
}

Output: As you can see, heavyOperation() is not being called on the name change.

Reactmemo

React.Memo

We have a Child component like this, Every time you click the increment-button to change count, the parent re-renders. Since Child is a regular component, it also re-renders even though name hasn’t changed.

interface ChildProps {
  name: string;
}

export const Child = function Child({ name }: ChildProps) {
  console.log('name chagned in child component');
  return <h3>Child Component: {name}</h3>;
};

Here, we can use React.memo. It wraps the Child component and tells React: “Only re-render this component if its props have changed.”

With React.memo, the Child component will not re-render unless name prop changes, even if the parent re-renders for other reasons, like count changing.

Modify the child component like this.

import React from 'react';
...

export const Child = React.memo(function Child({ name }: ChildProps) {
   ....
   ....
});

With React.memo

  • Now, React skips re-rendering the Child component when only the count changes.
  • The child re-renders only when the actual name prop changes.

Output: Now things are in perfect balance

Usereact

Conclusion

Using React.memo and useMemo doesn’t make your app faster by default but they’re powerful tools when you start hitting real performance costs. Use them wisely when

  • Your components are re-rendering too often.
  • You have heavy computations inside a component.
  • You want to avoid child updates unless props truly change.