Avoid Nested Declared React Components
Introduction
While React excels at simplifying UI development, it’s not foolproof against misuse by even experienced developers. One common mistake that even experienced developers make is declaring React components within other components. This practice, while seemingly innocuous, can lead to unexpected behavior and performance issues. In this post, we’ll explore why this is problematic and how to avoid it.
The Problem: Nested Declared React Components
Let’s start with a simple example to illustrate the issue:
function ParentComponent() {
function NestedComponent() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
return (
<div>
<h1>Parent Component</h1>
<NestedComponent />
</div>
);
}
At first glance, this code might seem fine. It works, and it might even pass a code review. However, it harbors a subtle but significant problem.
The Issue with State
When you declare a component inside another component, that inner component is recreated on every render of the parent component. This means that any state within the nested component is reset on each render.
To see this in action, check out this CodeSandbox. Try the following:
- Increase the count in both the “Bad” and “Good” lists.
- Add an item to both lists (click the “Add Item” button).
You’ll notice that the count resets in the “Bad” list, but in the “Good” list, the state persists.
Why This Happens
When you declare a component inside another component, it’s not really a component in the React sense. It’s just a function. On each re-render of the parent component, a new reference to this function is created. This means:
- The “component” loses its identity between renders.
- Any state or effects associated with this “component” are reset.
- React’s reconciliation process can’t optimize renders for this “component”.
The Solution: Declare Components Separately
The solution is simple: declare your components separately. Here’s how you can refactor the previous example:
function NestedComponent() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
function ParentComponent() {
return (
<div>
<h1>Parent Component</h1>
<NestedComponent />
</div>
);
}
By declaring NestedComponent
outside of ParentComponent
, we ensure that:
- The component maintains its identity across renders.
- State and effects work as expected.
- React can properly optimize renders for this component.
Best Practices
- Separate File: If your file is getting large (over 200-300 lines), consider moving child components to a new file.
- Same File: For smaller components, it’s fine to keep them in the same file as long as they’re not nested.
- Higher-Order Components: If you need to create components dynamically, consider using higher-order components or render props instead of nesting component declarations.
The Rules of Hooks
This issue is closely related to the Rules of Hooks in React. Hooks (like useState
and useEffect
) rely on a stable component identity to work correctly. When you nest component declarations, you’re inadvertently breaking these rules.
Further Reading
- Don’t call a React function component by Kent C. Dodds
- Video explanation of this issue
Conclusion
While it might be tempting to declare components within other components for the sake of organization or scoping, it’s a practice that should be avoided. By keeping your component declarations at the top level, you ensure that your components behave predictably and perform optimally.
Remember, in React development, clarity and predictability are key. By following these guidelines, you’ll create more maintainable and efficient React applications.