A Beginner's Guide to React Component Composition

Learn how to improve your React app architecture with component composition, one of the core principles of React development.

#javascript
#react
#frontend
A Beginner's Guide to React Component Composition
Picture by Majestic Lukas

When I started working with React, I immediately felt the flexibility that its API brings to front-end development. Creating a web component seems easier, and our ad-hoc elements do their job perfectly. As I began working as a JavaScript instructor at Codeworks and diving deeper into React, I also realized I was not properly exploiting the functionalities of this library, and I discovered there is much more than state and props for our components.

Knowing how to use the concepts of React, we could create incredibly flexible components able to fit multiple scenarios of our app. Inspired by Kent C. Dodds and his incredible contributions to the community, I want to help you to understand a different pattern to make your code more powerful.

What’s the problem?

We want to create a simple navbar that’s able to render a list of elements, and we want to show some of them only under certain circumstances. Let’s now assume that our navbar can show public elements to all the users, and some elements only to those who are logged in to the platform.

The items in the navbar will be complex, and it should be able to handle different scenarios seamlessly. We need to create a component that uses a single prop to decide if some elements should render or not. Let’s start creating your first compound component!

Specify what you need for your component

Since we want to create a navbar with public and private characteristics, we need the Navbar component. This component should receive a prop to determine if an element is public or private, so we can keep it simple. We’ll use an isLogged <boolean> prop.

At this point, it is essential to define the role of this component. It will render any children and pass them this property (or any other props if we grew the application). But here comes the first problem — we don’t want to pass this prop to every child or drill props down through components, especially because we could include other non-React elements. For this reason, we’ll use React.Context to fix it.

We want to define two other components as static properties of our Navbar functional component. Let’s call them Navbar.Item and Navbar.PrivateItem.

To recap what we have now:

  • Navbar
  • Navbar.Item
  • Navbar.PrivateItem

Now that we have the component skeleton, let’s add some logic by passing the component's props.

jsx
import React from 'react';

function Navbar(props) {
return props.children;
}

Navbar.Item = function (props) {
return <h1>I'm a public Item!</h1>;
};

Navbar.PrivateItem = function (props) {
return <h1>I'm a private Item!</h1>;
};

/**
* Usage of Navbar component
*/
function App(props) {
return (
<Navbar isLogged={true}>
<Navbar.Item />
<Navbar.PrivateItem />
</Navbar>
);
}

Pass all the props to every child

As said before, the Navbar component receives the isLogged prop, and we want to forward it to each child element, making them able to decide if it should render or not. But how do we pass this prop, or any other, to the children and avoid any form of prop drilling? Also, another problem we could face is the presence of a non-React element in the children list, and we don’t want to pass the isLogged prop to any div or whatever else!

To achieve the result, we make use of React.Context, providing the data to all the children and nested elements that need access to the isLogged prop. Doing so, we can consequentially create a conditional rendering for the sub-components:

jsx
import React, { useState, useContext } from 'react';

const NavbarContext = React.createContext();

function Navbar({ isLogged, children }) {
const [contextState] = useState({ isLogged });
// Pass a state as value to avoid a rerender of the children when isLogged doesn't change
return <NavbarContext.Provider value={contextState}>{children}</NavbarContext.Provider>;
}

Navbar.Item = function (props) {
const context = useContext(NavbarContext);
return <h1>I should render if {context.isLogged} is true and always!</h1>;
};

Navbar.PrivateItem = function (props) {
const context = useContext(NavbarContext);
return context.isLogged ? <h1>I should render if {context.isLogged} is true!</h1> : null;
};

/**
* Usage of Navbar component
*/
function App(props) {
return (
<Navbar isLogged={true}>
<Navbar.Item />
<Navbar.PrivateItem />
</Navbar>
);
}

At this point, we are free to insert as many public or private items into the Navbar, but always adding an H1 doesn’t seem meaningful. The real reason behind the usage of this component is to abstract the logic around the conditional rendering for the Navbar items. What matters now is being able to render whatever we want based on the logged condition.

Compose the composition

With a final touch, we’ll finalize the component for this purpose. What we want to do is render inside every item something of our choice, which could be some text as only a simple icon. To get the expected result it is necessary to render the children of each item, getting rid of the H1 tag we used before as an example:

jsx
import React, { useState, useContext } from 'react';

const NavbarContext = React.createContext();

function Navbar({ isLogged, children }) {
const [contextState] = useState({ isLogged });
return <NavbarContext.Provider value={contextState}>{children}</NavbarContext.Provider>;
}

Navbar.Item = function (props) {
return props.children;
};

Navbar.PrivateItem = function (props) {
const context = useContext(NavbarContext);
return context.isLogged ? props.children : null;
};

/**
* Usage of Navbar component
*/
function App(props) {
return (
<Navbar is Logged={true}>
<Navbar.Item>
<p>This paragraph should always appear, is for all the users!</p>
</Navbar.Item>
<Navbar.PrivateItem>
<p>You can see this paragraph only if you are an authenticated user!!!</p>
</Navbar.PrivateItem>
</Navbar>
);
}

The final touch

This is all you need to create a flexible compound component! This was a simple example, but you can compose your components following different and more complex logic. To finalize the component, this last snippet shows how I would personally write the entire component:

jsx
import React, { useState, useContext } from 'react';

const NavbarContext = React.createContext();

function Navbar({ isLogged, children }) {
const [contextState] = useState({ isLogged });
return (
<NavbarContext.Provider value={contextState}>
<div className="nav_container">{children}</div>
</NavbarContext.Provider>
);
}

Navbar.Item = function ({ children }) {
return <div className="nav_item">{children}</div>;
};

Navbar.PrivateItem = function (props) {
const { isLogged } = useContext(NavbarContext);
return isLogged && <Navbar.Item {...props} />;
};

export default Navbar;

Last updated: