DEV Community

Cover image for Compositions in React
Arkar Kaung Myat
Arkar Kaung Myat

Posted on • Updated on

Compositions in React

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.

Components in React

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>
Enter fullscreen mode Exit fullscreen mode

 Simple navigation

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>
    );
};
Enter fullscreen mode Exit fullscreen mode

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>
    );
};

Enter fullscreen mode Exit fullscreen mode

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>      
        );
};

Enter fullscreen mode Exit fullscreen mode

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 });
                    }
                }
            },
        };
    }
}

Enter fullscreen mode Exit fullscreen mode

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);
        };

    }
}

Enter fullscreen mode Exit fullscreen mode

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);
            }
        };

    }
}


Enter fullscreen mode Exit fullscreen mode

class Validator {
      {/* ...  */}
      {/* ...  */}
      {/* ...  */}
      {/* ...  */}
}

let validator = new Validator();
validator.decorate('hasName');
validator.validate({});
console.log(validator.error);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);

Enter fullscreen mode Exit fullscreen mode

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>);
    );
};

Enter fullscreen mode Exit fullscreen mode

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>}/>
    )
}

Enter fullscreen mode Exit fullscreen mode

And There we go !

Top comments (1)

Collapse
 
sarveshprajapati profile image
Sarvesh Prajapati

Great content....