Blog
Oct 8, 2026-7 MIN READ
Why Does a Component Still Re-render After Adding React.memo?

Why Does a Component Still Re-render After Adding React.memo?

Understand how object and function references affect React.memo, when useMemo and useCallback help, and how to optimize with practical examples.

Mohammadreza Khalili

Mohammadreza Khalili

Sometimes the data stays the same, but its reference changes.

Consider these two objects:

const previousUser = { name: "Alex" };
const nextUser = { name: "Alex" };

previousUser === nextUser;          // false
Object.is(previousUser, nextUser);  // false

They contain the same information, but they are separate objects.

By default, React.memo compares each prop with its previous value using Object.is. It doesn’t inspect an object’s nested properties to determine whether its contents are identical. React.memo reference

Here’s how that affects a component:

import { memo, useState } from "react";

const UserCard = memo(function UserCard({ user, options }) {
  console.log("UserCard rendered");

  return (
    <article>
      <h2>{user.name}</h2>
      <p>{user.role}</p>
      {options.showEmail && <p>{user.email}</p>}
    </article>
  );
});

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

  const user = {
    name: "Alex",
    role: "Frontend Engineer",
    email: "alex@example.com",
  };

  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>
        Count: {count}
      </button>
      <UserCard user={user} options={{ showEmail: true }} />
    </>
  );
}

Clicking the button updates count, so App renders again. Each execution creates new user and options objects.

The state update triggers the parent render. The new references prevent the default memo comparison from skipping the child’s render. Minimizing prop changes

Two details matter when trying this example:

  • It assumes React Compiler is disabled. With the compiler enabled, equivalent optimizations may happen automatically. React Compiler introduction

  • In development, StrictMode deliberately calls component bodies an extra time to detect impure rendering. You may see duplicate console logs; that doesn’t mean the DOM was updated twice. StrictMode documentation

Start with the simplest solution

If the data is static, define it outside the component:

import { memo, useState } from "react";

const USER = {
  name: "Alex",
  role: "Frontend Engineer",
  email: "alex@example.com",
};
const OPTIONS = { showEmail: true };

const UserCard = memo(function UserCard({ user, options }) {
  return (
    <article>
      <h2>{user.name}</h2>
      <p>{user.role}</p>
      {options.showEmail && <p>{user.email}</p>}
    </article>
  );
});

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

  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>
        Count: {count}
      </button>
      <UserCard user={USER} options={OPTIONS} />
    </>
  );
}

Now rendering App doesn’t recreate those objects.

Use useMemo when the value depends on changing inputs

When an object is derived from props or state, useMemo can reuse the calculated value while its dependencies remain unchanged:

import { memo, useMemo } from "react";

const UserCard = memo(function UserCard({ user }) {
  return (
    <article>
      <h2>{user.name}</h2>
      <p>{user.role}</p>
      <p>{user.email}</p>
    </article>
  );
});

export default function Profile({ name, role, email }) {
  const user = useMemo(
    () => ({ name, role, email }),
    [name, role, email]
  );

  return <UserCard user={user} />;
}

This is useful when preserving the reference enables a worthwhile optimization. Include every reactive value used in the calculation; omitting dependencies can leave you with stale data. useMemo reference

Sometimes you can remove the intermediate object entirely:

import { memo } from "react";

const UserCard = memo(function UserCard({
  name,
  role,
  email,
  showEmail,
}) {
  return (
    <article>
      <h2>{name}</h2>
      <p>{role}</p>
      {showEmail && <p>{email}</p>}
    </article>
  );
});

export default function Profile({ name, role, email }) {
  return (
    <UserCard
      name={name}
      role={role}
      email={email}
      showEmail
    />
  );
}

For these string and boolean props, unchanged values compare equal without memoizing an object. Simplifying props

Functions can also have changing references

An inline callback creates a new function whenever the parent executes:

<SaveButton onSave={() => saveUser(userId)} />

If the child is memoized and profiling shows that skipping its renders helps, useCallback can preserve the callback reference:

import { memo, useCallback } from "react";

const SaveButton = memo(function SaveButton({ onSave }) {
  return <button onClick={onSave}>Save user</button>;
});

export default function UserActions({ userId, saveUser }) {
  const handleSave = useCallback(
    () => saveUser(userId),
    [saveUser, userId]
  );

  return <SaveButton onSave={handleSave} />;
}

Here, saveUser is explicitly a prop, so it belongs in the dependency array. If it changes on every parent render, handleSave will change too.

If saveUser were instead a function imported at module scope, it wouldn’t be a reactive dependency.

useCallback caches the function itself, not the result of calling it. useCallback reference

A re-render isn’t automatically a performance problem

Rendering means React calls your component to calculate its output. React then determines which DOM changes, if any, are needed. An unchanged result may require no DOM update. Render and Commit

Also, memo is a performance optimization, not a guarantee that React will always skip rendering. Changes to the component’s own state or a context it consumes can still cause re-renders. React.memo documentation

Before adding more memoization, ask:

  • Which interaction is actually slow?

  • Is the expensive work rendering or a calculation?

  • Is an unstable reference defeating an existing optimization?

  • Could simpler props or more local state help?

  • Does profiling confirm an improvement?

Adding useMemo and useCallback everywhere can make code harder to maintain without improving the experience. When useCallback is useful

Finally, new references are often necessary. When an object in state changes, create an updated object and set the new state instead of mutating the existing object. Updating Objects in State

The goal is to understand where identity matters and whether stabilizing it solves a measured problem.

Before optimizing, ask: “What triggered this render, and is a new reference preventing the optimization I expected?”

© 2026 Mohamadreza. All rights reserved.