DEV Community

Newton Fernandis
Newton Fernandis

Posted on

Why You Shouldn’t Define a Component Inside Another Component in React

The Temptation

You’re building a dashboard and need a profile card for the logged-in user.
It’s quick, it’s simple, so you write:

function Dashboard({ user }) {
  function ProfileCard() {
    return (
      <div className="card">
        <h2>{user.name}</h2>
        <p>{user.role}</p>
      </div>
    );
  }

  return (
    <div>
      <h1>Dashboard</h1>
      <ProfileCard />
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

It works. The UI renders. You move on. 🚀
But lurking under the surface is a performance and state bug just waiting to bite.

The Hidden Problem

Every time Dashboard re-renders, whether because user changes or some unrelated state updates, React recreates the ProfileCard function.

That one fact breaks React’s reconciliation process and can lead to:

  • State loss inside ProfileCard
  • Extra unmount/mount cycles
  • Wasted DOM operations

Quick Refresher: What is Reconciliation?

Reconciliation is how React updates the UI without throwing everything away.

Steps simplified:

  1. Build a new virtual DOM tree from your latest render.
  2. Compare it to the previous virtual DOM tree.
  3. Update only the parts that changed in the real DOM.

For React to do this efficiently, it relies heavily on component identity.

How React Tracks Component Identity

In the virtual DOM, each element has a type property:

  • Strings for DOM nodes ('div', 'span', etc.)
  • Functions or classes for React components

Example:

{
  type: ProfileCard, // function reference
  key: null,
  props: { name: "Jane" }
}
Enter fullscreen mode Exit fullscreen mode

During reconciliation, React compares the type of the old element and the new element.
The type is the function or class reference for a component, or a string for a DOM element (like 'div' or 'span').

If the type changes, React assumes:

“This is a completely different component.”

When that happens, React will:

  • Unmount the old component (run cleanup effects, discard state).
  • Mount the new one from scratch (run the function body fresh, initialize hooks).

The Problem with Inline Components

When you define a component inside another component:

function Dashboard() {
  function ProfileCard() { /* ... */ }
  return <ProfileCard />;
}
Enter fullscreen mode Exit fullscreen mode

React creates a brand-new component on every render.

  • On render #1, type = ProfileCard@0x001 (memory address A)
  • On render #2, type = ProfileCard@0x002 (memory address B)

They may look the same in code, but they are different function objects in memory.

React’s check:

oldFiber.type !== newFiber.type // true
Enter fullscreen mode Exit fullscreen mode

And that triggers a full unmount + mount.

Result:

  • Any local state in ProfileCard is lost.
  • Effects run cleanup + re-run from scratch.
  • DOM nodes are destroyed and recreated.

Real-World Impact

Let’s say ProfileCard has a form for editing user info:

function ProfileCard() {
  const [editing, setEditing] = useState(false);
  // form logic...
}
Enter fullscreen mode Exit fullscreen mode

If Dashboard re-renders due to a theme change or notification count update:

  • Old ProfileCard is unmounted.
  • New ProfileCard is mounted.
  • editing resets to false.
  • Your user loses unsaved changes. 😬

The Correct Approach

Move ProfileCard out so its identity is stable:

function ProfileCard({ name, role }) {
  return (
    <div className="card">
      <h2>{name}</h2>
      <p>{role}</p>
    </div>
  );
}

function Dashboard({ user }) {
  return (
    <div>
      <h1>Dashboard</h1>
      <ProfileCard name={user.name} role={user.role} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now:

  • ProfileCard keeps the same reference across renders.
  • React preserves state and DOM.

Takeaway

React’s reconciliation is built on stable component identity.
Defining components inside other components breaks that stability, causing unnecessary unmounts and state loss.

Top comments (0)