Avoiding deeply nested component trees

By passing child components down instead of data you can avoid passing data down through many levels of components. It also makes your components more reusable. Even multibrand components become much easier to build. Overall it is a pattern which improves your frontend code a lot!

The Problem

When building frontends you will pass data from a parent component to a child component. Often the child component renders this data, but not the component passing it along. The child components have different data requirements than your current component.

Then you add a new component, somewhere down your component tree. It has new data requirements, so you have to pass its data through all its parent components. On top of data, it might also need callbacks to provide interactivity. You also pass these through all parent components. You change a lot of files to add new functionality. With all the data passing the readability of your code also decreases. Overall the maintainability of codebase decreases.

The code examples in this post are using React. But you can encounter the same issues in Angular, AngularJS, Vue, Polymer or any other component based frontend library.

The TabBar components is passing the tabs to its child component. However, it does not need the data for its own view rendering. In this example this is not a big issue. However, in real world applications you will have much more nesting. Then you end up just passing the data along through all these levels of nesting. This adds noise throughout your codebase.

In essence your component has multiple responsibilities. Next to its regular task of showing UI it is also passing data to its children. When we avoid this we can follow the single responsibility principle! A component is either passing data to child components OR it is rendering data to the screen.

The Solution

Instead of passing data to child components, you can pass child components to your current component. Parent components can provide these child components with their data . That way you move the responsibility of passing data to your parent component. Your component itself has become a container component. It is now only responsible for displaying its own data in the view.

Pass components down, not data

In the code below the TabBar component receives Tab components via the children property. As a side effect of making TabBar a container, it is now also a lot more reusable. We can pass in any child component!

This approach is a form of inversion of control. You might even consider it a form of dependency injection. The difference is however, that you are not giving up control to a framework, but instead to a different part of your own code.

This pattern can be applied in other frontend libraries as well. In Angular it is called transclusion. In Polymer / Webcomponents slots can be used inside shadow dom.

Taking the pattern further you will end up with a page component which connects state to all the components. This makes it the only type of component that is passing data down to child components. To make sure it still has a single responsibility we want to avoid also rendering UI in this component.

Depending on your application your page component might become rather big. Note, this only happens when are truly showing your user a lot of different components. If this is becoming a real issue you can of course trade in some flexibility and add in a level of nesting.

Passing multiple child components

Some components are not simple wrappers around its child components. They might contain child components at multiple locations. You can consider these places slots. Think of a header component which has a logo as well as a search bar. Both are child components of the header. To apply our component passing pattern to those components as well, we can do the following:

Multi brand components

A special form of reusability is using the same components across multiple brands. When you have a deeply nested component tree and you need to change a single leaf component you have 2 options. Either recreate the entire tree or make the leaf component configurable with an if/else construct.

When your tree is shallowly nested using container components, you just have to duplicate the page component and swap out the leaf. This type of duplication is not a bad thing. You can see it as the configuration of your page. Often you will be swapping out many components to create a new brand page. Therefore the actual duplication will be limited.

Conclusion

When you start off building your application it is doesn’t have much nesting and the code is easy to read. This changes when the application starts to grow. You recognize you might have an issue when you are editing many parent components just to get the data / callback you need. By passing components down instead of data you can remove the responsibility of passing data from your components. By doing less in your components, your code becomes easier to maintain and reuse.

In my experience we can apply this pattern to almost all components. The main use case where you see most value, is if you are building components which are reused for multiple brand variations of your app. If you are not doing this you can use the pattern to avoid your component tree to be too deeply nested.

I am interested to hear what you think and whether the pattern in this blogpost is something you have used in your projects. Contact me on Twitter or in the comments section.