Open-closed SOLID principle in React
— react, open-closed principle, composition, inheritance — 3 min read
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:
class Button extends React.Component { getButtonText() { return this.props.name; }
render() { return <button>{this.getButtonText()}</button>; }}
// Note that SmileyButton extends Button instead of React.Componentclass SmileyButton extends Button { getButtonText() { const text = super.getButtonText(); const smiley = this.props.smiley || ""; return `${text} ${smiley}`; }}
// Client codeconst 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:
const Button = ({ name }) => <button>{name}</button>
const SmileyButton = ({ name, smiley = "" }) => { const happyName = `${name} ${smiley}`; return <Button name={happyName} />;};
// Client codeconst 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:
const Button = ({ getButtonText = () => "" }) => <button>{getButtonText()}</button>
// Client codeconst 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!
References:
Thanks for reading my article. I am Roberto and I'm based in Madeira Island, Portugal. Get in touch via @RobertoRJ or email me at em.susejotrebor@tniopyrtne