React compound component with context API

In this example, our Todo app functions and rendering are contained within the Todos component in Todos.

js.

Our App component in src/index.

js simply render the Todos component, allowing it to handle all rendering logics.

We can see in the above example, that it would be quite difficult to reuse the Todo component as we have hardcoded the JSX within our todo component.

Compound Component to the rescueLet us refactor our example to implement our Todo component as a Compound Component.

You can find the full refactored example here.

In our new example, we now have the means to control the rendering of the different elements within the Todo component.

Here, you can change the rendering order of the elements.

We achieve this by using 2 React API, React.

children.

map and React.

cloneElement.

Using static properties, we allow the elements to be reference with dot annotation, and this in turn allow the consumer of the Todo component to explicitly declare them in their code.

In src/components/Todo/Todo.

js, we define our static properties to refer to the imported components for the elements in our Todo component.

The direct children of Todo component are iterated through React.

children.

map which allow us to access each children in this.

props.

children.

Within React.

children.

map, each children are identified via the displayName property, and a new transformed copy of the children is returned after passing in the props required by the children with React.

cloneElement.

Any children not identified will simply be returned and render as it is.

displayName is used for debugging purpose, but is particularly useful in identifying children in our exampleThe last step to make our Compound component work, is defining the displayName for the imported element components.

Now, we can manipulate the layout of the app without having to edit the Todo component itself, and we could have multiple instance of the Todo component each with it’s own unique layout.

We can also inject other element to be render together with components within the Todo component.

There are drawbacks to this approach though.

Using React.

children.

map and React.

cloneElement to loop thru and identify each children results in code that’s not exactly elegant in any way, and can be difficult to maintain in the long run when you have more elements within the Compound component.

React.

children.

map only return direct children, so you can’t nest the element in the consumer inside another element, otherwise any prop passing into the children will pass into the top most parent element instead and the intended children will not receive the prop.

Let us now see how to resolve these drawbacks with the React Context API.

Mixing in React Context APIWe will now refactor our Compound to use the React Context API.

You can find the complete example here.

Other than having the capability to nest the the Todo component’s elements in any other element, there’s no change to how we consume Todo component in our previous example.

Let’s have a look at src/components/Todo/Todo.

js again.

We define a new context for Todo named TodoContext.

This will give us the provider and consumer for Todo.

Next we declare and export a constant named TodoConsumer that will be import by the components of the elements in Todo in order to “consume” the values provided by the provider.

In place of the lengthy code that loop the children with React.

children.

map, we have a single provider that pass in the data required by our children.

Note that we define all our data that is to be pass to the provider’s value prop in the Todo component’s internal state.

This is to prevent the data object from being recreated every time the component renders, even if none of the values changes.

This will cause the children component to re-render itself unnecessarily and undermine the performant of the Todo component.

The last step is to modify the elements component to “consume” the data passed in by the context provider.

The context consumer uses the render prop method to pass in the required props to a function, which in turn returns a React element.

Because Context API allows any nested component in multiple levels under the provider to have access to the value prop, we can nest our Todo component’s elements in any other element and the required props will still be receive by the consumer regardless of the nesting order, solving the drawback of our original compound component with direct children.

Delegating the task of “picking up” props from the provider’s value prop to the context consumer in the element component also result in code that’s easier to understand and maintain.