So today I want to talk about what is composition and why composition patterns in React make react component system more powerful.
When I started learning React. One of the main features of React that excited me is that it is component-based!
So today I want to talk about what is composition and why composition patterns in React make react component system more powerful.
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?
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's requirements with this approach.
Imagine in the morning you got an assignment in your ClickUp 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!
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!
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({ name: validator.name });
}
}
},
};
}
}
Here we have a validation class for our form validation library which we gonna look at 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({ name: validator.name });
}
}
},
};
}
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 !
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 are functions 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 !
Join my web development newsletter to receive the latest updates, tips, and trends directly in your inbox.
How I keep learning new things, trying stuff out, and staying motivated as a self taught dev with one project idea.
Check out this blog post on using Next.js 13 server actions and Mailgun! Don't wait, start building your own newsletter today and share your passion with the world!
A brief catch up on what discussed at var.camp.