Writing Clean and Performant React Components: Why You Should Define Subcomponents and Handlers Outside
In React development, clarity and performance are crucial. As our applications grow, a clean structure becomes just as important as speed and efficiency. One common discussion point among developers is:
“Should I define subcomponents and event handler functions inside or outside the main component function?”
This article explores the best practices, trade-offs, and performance implications of that choice, and why many experienced developers—including myself—prefer to define subcomponents and event handlers outside the main function body.
✅ What We Mean by “Defining Inside” vs. “Outside”
Let’s start by clarifying the distinction.
Defining inside the component:
export default function About() {
function SubTitle() {
return <h2>This is a subtitle</h2>;
}
const handleClick = () => {
console.log('Clicked!');
};
return (
<div>
<h1>About Page</h1>
<SubTitle />
<button onClick={handleClick}>Click</button>
</div>
);
}
Defining outside the component:
function SubTitle() {
return <h2>This is a subtitle</h2>;
}
export default function About() {
const handleClick = () => {
console.log('Clicked!');
};
return (
<div>
<h1>About Page</h1>
<SubTitle />
<button onClick={handleClick}>Click</button>
</div>
);
}
In both cases, the code works. But there are subtle differences in behavior and maintainability.
💡 Why You Should Define Subcomponents Outside
1. Cleaner and more readable structure
Placing subcomponents outside the main component function declutters your logic. This separation follows the Single Responsibility Principle: each function does one thing, and your main component focuses solely on layout and flow.
Cleaner structure makes onboarding and debugging much easier.
2. Avoids function recreation on every render
When you define a subcomponent or handler inside the main component function, it gets redeclared on every render. That’s typically harmless for small components, but it becomes a performance bottleneck in larger trees or with memoized children.
function Parent() {
function Child() {
console.log("Rendered");
return <div>Child</div>;
}
return <Child />;
}
Every time Parent
renders, Child
is recreated—breaking React.memo
, reinitializing hooks, etc.
3. Enables React.memo
and optimization
You can't memoize a component properly if it's recreated on every render. Defining it outside allows you to use:
const SubTitle = React.memo(function SubTitle() {
return <h2>This is a subtitle</h2>;
});
This is extremely useful when your UI grows or your app becomes performance-sensitive.
4. Easier testing and reuse
When your components and handlers are defined separately, you can test them in isolation or reuse them in different places without duplication. This isn't possible if they're buried inside another component.
✅ Why You Should Define Handlers Outside the Render Scope
We often write event handlers like this:
<button onClick={() => setCount(count + 1)}>Click</button>
It’s quick—but it creates a new function on every render. For simple apps, this is acceptable. But for apps with many interactive components, this can trigger unnecessary re-renders.
A cleaner and better-performing version:
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []);
If your handler doesn't use any props/state, you can even move it fully outside the component.
⚖️ When It’s Okay to Define Inside
There are times when defining a function or subcomponent inside is just fine:
- The function is very small and scoped exclusively to the component
- There are no performance bottlenecks
- You're doing quick prototyping or demos
But even then, be aware that you're sacrificing some performance and structure for convenience.
🧠 Other Considerations You May Overlook
- Debugging becomes harder when many functions are defined inline. Stack traces are less clear.
- Component files become too large and harder to manage if everything is crammed into one function.
- With custom hooks, defining handlers inside can interfere with memoization and lifecycle expectations.
- If you're using server components (e.g., in Next.js 14+), function scope affects serialization behavior.
🧾 Final Recommendation
Define your code with the future in mind:
- 🔹 Keep subcomponents outside the main function
- 🔹 Keep event handlers separate, use
useCallback
if necessary - 🔹 Avoid defining things inline unless it's trivial and has no impact
- 🔹 Think in terms of reuse, testability, and readability
This approach not only makes your codebase more maintainable but also makes your React application faster and easier to optimize later on.
🏁 Finally
Writing performant and scalable React components isn’t just about choosing the right tools—it’s also about code structure. By defining subcomponents and event handlers outside the main component, you’re embracing a practice that improves readability, performance, and maintainability in the long run.
It’s a small decision with big architectural impact. And like all good engineering decisions, it pays off as your app grows.
Comments ()