Why Composition Beats Inheritance In React
Learn the benefits of using composition over inheritance in React projects. (3 minutes)
A month ago, I shared an article about writing extensible components in React by following the Open-Closed Principle.
I slightly touched on the importance of preferring composition over inheritance when crafting UI components.
If you want to create UI components that grow with your app, choosing the right strategy from the start is crucial.
In today’s article, I’ll dig deeper into the importance of using composition over inheritance.
Even React’s team recommends this approach: “composition over inheritance”, which aligns perfectly with OCP.
If you missed the previous article on Open-Closed Principle (OCP) In React: Write Extensible Components, check it out here:
The Limits Of Inheritance
Let’s imagine a simple `<LoadingButton />` built by extending our base`<Button />` from the previous article:
// Inheritance-based approach (less flexible)
class LoadingButton extends Button {
render() {
return (
<Button className={this.getButtonClass()}>
{this.props.isLoading && <Spinner />}
{this.props.children}
</Button>
);
}
}
Even though it looks okay at first, there’re a couple of problems with this approach:
Tight Coupling - every change in the `<BaseButton />` logic risks breaking the `<Button />` subclass.
Flat Hierarchies - adding new variants leads to a growing class tree.
Hard To Mix Behaviors - combining features like icon + loading needs new subclasses.
How Composition Works
The main idea behind composition consists of two steps:
Break the UI into many smaller reusable components.
Combine these reusable components into more complex components.
You can learn more about how to turn your UI design into React Components here:
This way, you have multiple smaller building blocks which can be reused across the codebase to fulfill different use cases.
const LoadingButton = ({ isLoading, children, ...restProps }) => (
<Button {...restProps}>
{isLoading && (
<Spinner />
)}
{children}
</Button>
);
// or
const BackButton = ({ navigateTo, ...restProps }) => {
const navigate = useNavigate();
const handleClick = () => {
if (navigateTo) {
navigate(navigateTo);
} else {
navigate(-1);
}
};
return (
<Button
{...restProps}
onClick={handleClick}
>
<FontAwesomeIcon icon={faLongArrowLeft} />
</Button>
);
};
Benefits Of Using Composition Over Inheritance
The composition based approach:
Makes components open for extension through props, render functions, etc.
Keep base components closed for modification.
Allows for unlimited mixes of behaviors and combinations.
This is how the idea of “composition over inheritance” aligns with OCP.
📌 TL;DR
Composition keeps core components unchanged and lets you add features via props or small wrappers.
Inheritance forces new subclasses for each variant, leading to less flexible components and more code.
By using composition, you can mix different behaviors like <LoadingButton />, <BackButton />, etc, without touching the core/base components.
Prefer Composition over Inheritance in React projects.
That's all for today. I hope this was helpful. ✌️
How I can help you further?
Become the Senior React Engineer at your company! 🚀
👋 Let’s connect
You can find me on LinkedIn, Twitter(X), Bluesky, or Threads.
I share daily practical tips to level up your skills and become a better engineer.
Thank you for being a great supporter, reader, and for your help in growing to 24.4K+ subscribers this week 🙏
You can also hit the like ❤️ button at the bottom to help support me or share this with a friend. It helps me a lot! 🙏