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-componentsmemo(...)forwardRef(...)
inside another React component unless you explicitly understand the remount consequences.
If a component should survive renders, its declaration must also survive renders.