Compositions in React
Arkar Kaung Myat
Posted on February 19, 2022
When I started learning React. One of the main features in React that excited me is that it is a component-based!.
So today I want to talk about what is composition and why composition patterns in React make react component system more powerful.
Components in React
In earlier days, the developers write more than a thousand of code for developing a single-page application. While following the traditional DOM structure making changes in them was
very challenging. Whether if you want to update, modify some of them, or even you want to fix some errors in them, it is very difficult to do.
You have to search for them and update
them manually. And then the component-based approach was introduced to overcome these issues. The idea is to divide the application into small logical groups and reuse them in different
places. Just like LEGO blogs, you create a component for each logical group and combine them into a bigger component.
Consider this example !
Imagine you are building the above navigation. The header has a logo and some navigation links to navigate around.
So basically there are logo and navigation components inside our header component.
<Header>
<Logo />
<Navigation />
</Header>
Well, this looks pretty ok until your designer or project manager wants to add a search bar or Christmas wish inside the navigation.
As we are not in the ideal world of software development, it is almost impossible for designs or features to be permanent.
Maybe we want to have our header have a search bar on a specific page or we want to have a Christmas wish on some pages or even we want our header to be
empty in specific cases.
What that means is that some component cannot always know their children
So how do we get the flexibility for our so-called reusable component?
Containment in Recuse
In React, we have special children props to help us solve this problem.
The idea is instead of creating hard-coded dependencies inside our component what if we can somehow dynamically pass what is gonna be inside our component's output.
function LOGO() {
return (
<h1>LOGO</h1>
);
};
function Navigation() {
return (
<nav>
// nav items
</nav>
);
};
function Header(props) {
return (
<header>
{props.children}
</header>
);
};
function App() {
return (
<Header>
<LOGO />
<Navigation />
</Header>
);
};
Notice that if we do not use {props. children} inside our header component, we will only get a plain header in our output.That makes our header component
much more agnostic and dependency-free.
Let's see if we can satisfy our product manager requirements with this approach.
Imagine in the morning you got an assignment in your click up or Slack saying
Hey!
I want a new year promotion banner on top of our pricing page navigation !!!
Well you can just create a component for the banner and
// upcoming feature announcement
function Banner() {
return (
<div>
{/* banner content */}
</div>
);
};
function Header(props) {
return (
<header>
{props.children}
</header>
);
};
function App() {
return (
<Header>
<Banner/>
<LOGO />
<Navigation />
</Header>
);
};
Well, that works better!
Specilization
Sometimes some components can possibly be a special case for other components.
During our previous example what if your product manager says
Hey! For our first 100 clients who took our new year promotion, I want them to have a text saying Hey you get a free t-shirt !.
In React, this is also achieved by composition, where a more โspecificโ component renders a more โgenericโ one and configures it with props:
// upcoming feature announcement
function Banner({getShirt,message}) {
return (
<div>
{getShirt && <p>{message}</p>}
{/* banner content */}
</div>
);
};
function Header(props) {
return (
<header>
{props.children}
</header>
);
};
function SpecialBanner(props) {
function getShirt (){ /* free t shirt logic */ }
return (
<Banner getShirt={getShirt} message={'Hey you get a free t shirt !'} />
);
};
function App() {
return (
<Header>
<SpecialBanner/>
<LOGO />
<Navigation />
</Header>
);
};
That's great! The more I read about react the more I fall in love with it!
More on composition
We also have HOC(Higher Order Components) to compose react components !!
Before we talk about it, let me talk about what I learned from
Javascript design patterns : Decorators
A Decorator is an object which adds functionality to another object dynamically.
Let's say you are writing a form validator for your app!
When filling out a form you want to make sure your users fill up some of the essential fields. You want to have a distinctive validation between essential ones and non-essential ones.
If one field is essential in the form, we would want a special function to check whether it's empty or not in our validator.
class Validator {
constructor(){
this.error = [];
this.data = []
this.decoratorsList = [];
this.decorators = {
hasName: {
validate: function (validator) {
// do all validation with the args here
if (!validator.name) {
this.error.push({ code: 'Name is required' });
} else {
this.data.push({ name: validator.name });
}
}
},
hasAge: {
validate: function (validator, args) {
// do all validation with the args here
if (!validator.age) {
this.error.push({ code: 'Age is required' });
} else {
this.data.push({ age: validator.age });
}
}
},
};
}
}
Here we have a validation class for our form validation library which we gonna look upon the form data we provide. If anything in the form is missing,
it gonna add up the error message to our validator class's error property or if everything is correct it is gonna add up the data
into our validator class's data property.
So how do we validate the form data?
class Validator {
constructor(){
{/* ... */}
{/* ... */}
decorate(name) {
this.decoratorsList.push(name);
};
}
}
We add a decorate method to our validator class which takes a name as an argument.Now that we have a method to add decorators(our dynamic form field that we want to validate)
we can loop through our decoratorsList and call each decorator's validate method to finally all the validations.
class Validator {
constructor(){
{/* ... */}
{/* ... */}
{/* ... */}
validate(form) {
let i,len,name;
this.form = form;
for (i = 0, len = this.decoratorsList.length; i < len; i++) {
name = this.decoratorsList[i];
this.decorators[name].validate.call(this,form);
}
};
}
}
class Validator {
{/* ... */}
{/* ... */}
{/* ... */}
{/* ... */}
}
let validator = new Validator();
validator.decorate('hasName');
validator.validate({});
console.log(validator.error);
Our finalized code gonna be like
class Validator {
constructor() {
this.error = [];
this.data = []
this.decoratorsList = [];
this.decorators = {
hasName: {
validate: function (validator) {
// do all validation with the args here
if (!validator.name) {
this.error.push({ code: 'Name is required' });
} else {
this.data.push({ name: validator.name });
}
}
},
hasAge: {
validate: function (validator, args) {
// do all validation with the args here
if (!validator.age) {
this.error.push({ code: 'Age is required' });
} else {
this.data.push({ age: validator.age});
}
}
},
};
}
decorate(name) {
this.decoratorsList.push(name);
};
validate(form) {
let i, len, name;
this.form = form;
for (i = 0, len = this.decoratorsList.length; i < len; i++) {
name = this.decoratorsList[i];
this.decorators[name].validate.call(this, form);
}
};
}
let validator = new Validator();
validator.decorate('hasName');
let formData = {
name: 'Riley',
};
validator.validate(formData);
console.log(validator.data)
console.log(validator.error);
As you can see we don't have any error on not putting the age in our form data because we haven't decorated the age decorator to our validator yet.
Try replacing an empty object in form data and you should see some results !!!
And this is how I got to understand what decorators are about and I hope you got the basic idea of them
please check out more snippets here
Higher order components in React
HOC is usually a function that takes a component and returns a decorated or enhanced version of it.
In our previous example, we said we need a banner with a special case for a free t-shirt.
let Banner = () => {
return <div>New year promotion is out Now !!!</div>
}
let decoratedComponent = (Component) => {
class Decorate extends React.Component {
constructor(props) {
super(props);
this.props = props;
}
render() {
return <SpecialWrapper>
<Component {...this.props} />
</SpecialWrapper>
}
}
}
let SpecializedBanner = decoratedComponent(Banner);
The first thing that the decoratedComponent does is render the original component that we passed in our decoratedComponent function and
then we can do special decorations and modifications by wrapping or enhancing it.
Render props in React
Render props are basically a function inside a render method.
Check out the code below!
let Todos = ({children,data})=>{
return <section>
<h1>A list of Todos</h1>
<ul>
{
data.map((todo, i) => (
<li key={i}>
{ children(todo) }
</li>
))
}
</ul>
</section>
}
export default function App() {
let [todos, setTodos] = React.useState([]);
let handleDone = () => {
// manageing the global state
}
let handleRemove = () => {
// manageing the global state
}
return (<Todos data={todos}>
{
todo => todo.done ? <button onClick={handleRemove}>Remove ! {todo.label}</button>
: <button onClick={handleDone}>Done ! {todo.label}</button>
}
</Todos>);
);
};
The App component has every data and logic. And the TodoList has no idea about what our todo may look like and it is just an encapsulation of our HTML markup.
let ContentProvider = ({render,data})=>{
return(
<section>{ render(data) }</section>
)
};
let App = ()=>{
let [data, setData] = React.useState({});
return(
<ContentProvider data={data} render={(data)=><p>{data}</p>}/>
)
}
And There we go !
Posted on February 19, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.