React Pitfall: Declaring Components Inside Other Components

There is a React mistake that looks completely harmless, passes TypeScript, works in development… and can silently break your application.

Especially if you use styled-components.

This code looks normal:

export const Layout = ({ children }) => {
  const Main = styled.main`
    min-height: 100dvh;
  `
  return <Main>{children}</Main>
}

Many developers write code like this for years without realizing there is a serious problem.

But React does not see this as “the same component”.

It sees a brand new component on every render.

What Actually Happens

This line:

const Main = styled.main``

creates a new React component.

Not updates. Not reuses.

Creates.

And because it is inside another component, it runs again every time the parent re-renders.

So React gets something like this:

Render 1 -> <Main_v1 />
Render 2 -> <Main_v2 />
Render 3 -> <Main_v3 />

For React, these are different component types.

And when component type changes, React unmounts the old tree and mounts a new one.

That means:

  • state resets
  • effects rerun
  • refs reset
  • animations restart
  • focus may disappear
  • forms can lose data

The Worst Part

The bug usually looks unrelated to rendering.

You may spend hours debugging:

  • React Hook Form
  • Framer Motion
  • Zustand
  • Tabs
  • Modals
  • Context
  • Memoization

while the real problem is just this:

const Something = styled.div``

inside a render function.

Real Example

Imagine this:

export const Page = () => {
  const Wrapper = styled.div`
    padding: 20px;
  `
  const [count, setCount] = useState(0)
  return (
    <Wrapper>
      {' '}
      Counter: {count} <br />
      <button onClick={useCallback(() => setCount((count) => count + 1), [])}>
        {' '}
        Update{' '}
      </button>{' '}
    </Wrapper>
  )
}

You click the button.

Page re-renders.

Wrapper becomes a new component.

React remounts everything inside it.

Your Counter state may reset completely.

This becomes even more painful with:

  • forms
  • editors
  • animations
  • expensive components

Correct Version

Move component declarations outside:


const Wrapper = styled.div`
  padding: 20px;
`

export const Page = () => {
  const [count, setCount] = useState(0)
  return (
    <Wrapper>
      {' '}
      Counter: {count} <br />
      <button onClick={useCallback(() => setCount((count) => count + 1), [])}>
        {' '}
        Update{' '}
      </button>{' '}
    </Wrapper>
  )
}

Now Wrapper has a stable identity.

React can reconcile correctly.

No remounts.

No random state loss.

This Is Not Only About styled-components

The same problem exists here too:

function Parent() {
  function Child() {
    return <div>Hello</div>
  }
  return <Child />
}

or:

const Child = () => <div />;

inside another component.

Nested component declarations are unstable by definition.

Why This Is Dangerous

Because:

  • ESLint often misses it
  • TypeScript does not warn
  • React does not warn
  • the UI may “mostly work”

Until one day:

  • your form randomly clears itself
  • animations flicker
  • modals reopen
  • state disappears
  • components remount endlessly

And debugging becomes a nightmare.

Rule of Thumb

Never declare:

  • React components
  • styled-components
  • memo(...)
  • forwardRef(...)

inside another React component unless you explicitly understand the remount consequences.

If a component should survive renders, its declaration must also survive renders.