Skip to content

Open-closed SOLID principle in React

react, open-closed principle, composition, inheritance1 min read

Open-closed principle is fundamental in extending software entities and React components are no different, check out how to do it!

Recently I read a very good article¹ published by a co-worker which demonstrate how to apply SOLID principles on react applications. In the article, the Open-closed principle is demonstrated using composition which I believe to be the de facto, and the best method to extend React components, although the principle usually refers to classical inheritance².

So I decided to write this article to exemplify how to apply the Open-closed principle using both inheritance and composition.

As a naive example let's create a simple Button component that gets a single prop name. A new component, SmileyButton should extend the previous and get a new prop smiley. This prop will be concatenated to the button's name.

If to be compliant with the Open-closed principle, it required to use class-based inheritance, this is how we could design our example:

1class Button extends React.Component {
2 getButtonText() {
3 return;
4 }
6 render() {
7 return <button>{this.getButtonText()}</button>;
8 }
11// Note that SmileyButton extends Button instead of React.Component
12class SmileyButton extends Button {
13 getButtonText() {
14 const text = super.getButtonText();
15 const smiley = this.props.smiley || "";
16 return `${text} ${smiley}`;
17 }
20// Client code
21const HappyButton = <SmileyButton name="Happy Button" smiley=":D" />

The principle is properly applied as we didn't modify the original Button component and still added a custom behaviour!

What about using composition? Composition is about what it does over what it is as in inheritance. Saying that, let's create functional components and compose them to achieve the same result as before:

1const Button = ({ name }) => <button>{name}</button>
3const SmileyButton = ({ name, smiley = "" }) => {
4 const happyName = `${name} ${smiley}`;
5 return <Button name={happyName} />;
8// Client code
9const HappyButton = <SmileyButton name="Happy Button" smiley=":D" />

We achieved the same result as the previous example! We just composed the Button component with the custom behaviour that we need! In my opinion, composition is more flexible, clean and future proof than inheritance, and joining it with react functional components... Damn, it's beautiful!

Note composition is not exclusive of Functional Programing paradigm, it's often recommended using it over inheritance in Object-Oriented Programming.

We aren't finished yet! There is another way of extending a component behaviour by taking a functional approach again. This time original Button will provide an API that lets us change its own behaviour:

1const Button = ({ getButtonText = () => "" }) =>
2 <button>{getButtonText()}</button>
4// Client code
5const HappyButton = <Button getButtonText={() => "Happy Button :D"} />

In this last example, we don't need to compose the button component as the client will provide the behaviour right away.

Here is a sandbox with working samples for all examples provided above:

Wrapping up, the composition is the way to go when extending functionality of React components, and guess what, High Order Components (HOCs) are a perfect example of composition. Other than that, remember that React class components are deprecated and will fade away soon! Leave a comment bellow and share your feedback!

  1. Applying SOLID To React
  2. Open-closed principle
Edit me on GitHub
author @RobertoRJ
Thanks for reading my article. I am Roberto and please share your feedback below.
Based in Digital Nomad. Get in touch @RobertoRJ or email me at em.susejotrebor@tniopyrtne