Enhancing React Components: Inheritance & Composition
Building React components is fun, but it can get tedious too!
One look at material-ui would tell us how beautiful components can be built with React. Building components is very easy in React, because well, components
is the USP of React, and Facebook has done everything in its power to make building components easy. However, well, there are still challenges.
Most web pages need various styles of a single component. For example, a button in one component is red in color, and in another, it's blue with a black border. A navigation bar looks full width on a desktop, but its full-height collapsible in mobiles. Components which are so similar, yet different. Do we end up writing two buttons and two navbars each time?
No! There should be another way. What are some ways to write lesser code and more flexible components?
There are a couple of ways of enhancing React components.
- Inheritance
- Composition
Now, understanding the pros and cons of both techniques is beyond the scope of this tutorial. However, this video serves as a good background to what these 2 methods are and how they stack up against each other.
Inheritance in React (or Plain JavaScript Classes)
Inheritance is a concept in JavaScript which allows one class to inherit another class's properties to duplicate behavior and add more features. For example, a Label
component can be extended into a SuccessLabel
with two additional properties background: green
and color: white
.
Similarly, an ErrorLabel
can be extended from Label
with its own additional properties like color: red
and so on and so forth.
But the bottom line of inheritance is that a SuccessLabel
instance is a Label and inherits the behavior of a Label
instance.
Also, a SuccessLabel
can be further extended to create an even more enhanced class and this process can go further levels deep. No matter how many levels deep we go, the resulting instance will still have the entire behavior of Label
within it and instanceof
operator will return true
when used with a SuccessLabel
instance against the Label
class.
Alright, so how would we use inheritance with React components?
Before we dive into the JS part, for the course of the tutorial, our CSS is as shown below.
CSS
html,body{
padding: 20px;
}
h3{
margin : 40px 0 20px;
}
hr{
border-color : transparent;
margin : 10px;
}
.plain-label{
font-size: 16px;
background : #f5f5f5;
border: 1px solid #333;
padding: 4px 8px;
}
.success-label{
background : green;
color : white;
}
.error-label{
background : red;
color : white;
}
To inherit one React component class from another, we can write the code like so:
Javascript
class Label extends React.Component{
constructor(props){
super(props);
this.className='plain-label';
}
render(){
return <span className={this.className}>
{this.props.children}
</span>
}
}
class SuccessLabel extends Label{
constructor(props){
super(props);
this.className = this.className + ' success-label';
}
}
class SuccessLabelWithIcon extends Label1{
constructor(props){
super(props);
this.className = this.className + ' success-label';
}
render(){
return <div>
{super.render()}<span>▲</span>
</div>
}
}
class ErrorLabel extends Label{
constructor(props){
super(props);
this.className = this.className + ' error-label';
}
}
class Main extends React.Component{
render(){
....
<Label> Plain Label </Label>
<hr/>
<SuccessLabel> Success Label </SuccessLabel>
<hr/>
<ErrorLabel> Error Label </ErrorLabel>
<hr/>
<SuccessLabelWithIcon> Success Label with Icon </SuccessLabelWithIcon>
...
}
}
Result
The code above relies heavily on the Label
component and its render
method. The render
method relies on this.className
(set within the constructor
for each extended class) property which is different for different extended classes and hence the styling varies with the CSS attached to the className
rendered.
One of the disadvantages of inheritance is clearly visible in the last case.SuccessLabelWithIcon
class is a SuccessLabel
but aims to show an icon to the right of the label
text. However, with the current code, it is unable to do so. Unless we do something like this.
//SucessLabelWithIcon Component
....
render(){
return
.....
{super.render(<span>▲</span>)}
.....
}
//SuccessLabel Component
....
render(args){
return
.....
//do something with args
.....
}
Now, this can't be the right way to do it. Because SuccessLabel
has to accommodate the use cases of another class.
More use cases => more arguments and this becomes unmaintainable very soon.
Inheritance
is in most scenarios — just not good a viable solution. Because extending components with inheritance more or less leads to a scenario at some point, where the behaviors cannot be clubbed seamlessly. However, with composition, its possible.
Composition in React
Composition is a technique which allows us to combine one or more components into a newer, enhanced component capable of greater behavior. Instead of extending another class or object, composition only aims at adding new behavior.
A SuccessLabel
is not a Label
but another component which happens to use a Label
within it. The properties of Label
are not inherited, only the behavior. Which means adding an Icon
to a SuccessLabel
should be fairly easy as well.
Let's try composition now.
JavaScript
class Label extends React.Component{
render(){
return <span className={this.props.className + ' plain-label'}>
{this.props.children}
</span>
}
}
class SuccessLabel extends React.Component{
render(){
return <Label className='success-label'>{this.props.children}</Label>;
}
}
class ErrorLabel extends React.Component{
render(){
return <Label className='error-label'>{this.props.children}</Label>;
}
}
class SuccessLabelWithIcon extends React.Component{
render(){
return <div>
<Label className='success-label'>{this.props.children}<span>▲</span></Label>
</div>
}
}
class Main extends React.Component{
render(){
....
<Label> Plain Label </Label>
<hr/>
<SuccessLabel> Success Label </SuccessLabel>
<hr/>
<ErrorLabel> Error Label </ErrorLabel>
<hr/>
<SuccessLabelWithIcon>SuccessLabel With Icon </SuccessLabelWithIcon>
...
}
}
Result
That was simple and easy, wasn't it? There are many ways of composing your React components and enhancing their behaviors. Since composition only aims at adding new behavior in a can-do
fashion instead of is-a
, it is just a matter of deciding which behaviors your component needs and just clubbing them together.
Similarly, here is a link to Codepen about what we have discussed in this tutorial. You can also read up on other important topics about React, like testing, in Jest and Enzyme or Karma and Webpack.
Happy coding!!
good explanation
Great Explanation. But what if SuccessLabel, ErrorLabel were more complicated components that need to handle some specific logic related to a Label. Wouldn’t inheritance be better in this case ?
Both SuccessLabel and ErrorLabel could use the share code handled by the parent class.
then we have more props for label to handle those complexity
I don’t think this the correct answer. Would have been ok if you mentioned HOC as an alternative but I still haven’t heard a valid argument as to why inheritance is no recommended in React. But thanks for this post. It was useful
The best explanation I’ve found so far Inheritance vs Composition in React…thanks!!!