The React Docs have a great section on Lifting State Up for cases where several components share the same changing data. The recommendation is to lift the shared state up to the components’ closest common ancestor. The state is then passed as props to the desired descendant components.

When dealing with props, quite often you will be passing the same props to sibling/descendant components (as may be the case when lifting some state up to a common ancestor). Consider the below example of a Header component:

We have a Header component with child Navigation and UserPanel components who both need access to the type prop, which we assume to be either horizontal or vertical . The Navigation and UserPanel components will then be able to render different layouts depending on this value.

Looking inside the child components, we can see how we might use the type prop:

Inside the Navigation component, we are rendering a more prominent callout section with the logo when the type is set to vertical . We would do something similar with the UserPanel component too; we can imagine rendering a slightly different layout depending on whether the parent Header is vertical or horizontal .

The Issue

The issue now as I see it is that we are repeating the passing of the type prop to descendant components; each descendant component that needs the type value must be passed it as a prop manually. Whilst this explicitness is arguably desirable, I’d argue that being able to implicitly access the type value from a parent/ancestor is a better experience, and allows for cleaner, DRY-er JSX. What this essentially means in practice is being able to implicitly access parent props. Another way of thinking about parent props is context.

Seeing as React has a Context API, we must surely be able to concoct an API that is desirable. Using the raw context API to achieve what we want would be quite cumbersome; we would have to create and export a context object in the Header component and spread the props into it. Then, in any descendant components that need access to the value, we would have to import the context and use the useContext hook.

This would be a worse experience than simply passing the prop to each descendant that needs it; the goal is to remove any coupling of the components caused by prop-drilling — by importing a dedicated context object every time, the components are still coupled. The only way to get around this coupling would be to use a global state/context manager (either something like Redux, or a global context object).

A Different Approach

In the same spirit as lifting state up to the nearest common ancestor, what we’d want here for our example is something like:

From a DX/API perspective, it makes more sense for the prop to be passed once to the nearest common ancestor instead of multiple times to each descendant. Inside our child components, we would now access the type via context instead of props , so we would need to supply a second argument to the component’s function (with everything else being the same thanks to destructuring the parameter objects):

In this train of thought, we would have inherent access to the props of our parent components. From a technical perspective, however, it’s not very do-able, unfortunately. Inherently accessing parent information in React is generally frowned upon, with the recommendation being to explicitly pass data down. Inherently accessing the props of the parent div in this particular case is essentially impossible. Without having access to div 's prototype, we can’t do anything useful internally with the type prop here (such as passing it to some context object).

If we had some sort of higher-level-component to handle all the dirty work, and we were willing to use said component for rendering elements to the DOM, we can end up with the following JSX:

All we have done is replaced the parent div wrapper with a custom Component component. We could have added a new wrapper/provider component to surround the existing div , but given that any UI component would have some sort of wrapping HTML element anyway, it’s more pragmatic to not introduce more nesting.

This custom component would maintain its own context object under the hood (for each instance), passing its props. Of course, in order to receive the context object, descendant components would also need to utilise the custom Component , which would leave the Navigation component looking something like:

Now unfortunately with this method, as it’s the <Component> that provides the context, we can’t pass the context argument as a second parameter to our component’s function and expect to get access to it. The best we can do is by using render props (or more specifically, a function as a child), so something like:

…which, if you ask me, isn’t really much worse, it’s still a very pleasant experience. This leaves us in a position of being able to inherit parent props, or even any ancestral props depending on how our custom Component component is built, without having to faff around manually creating, exporting and importing context objects.

<Component>

You could create a custom component to achieve this functionality fairly easily. The end result may look something like:

…you can see how internally we utilise the context API to provide parent props to components. By extracting this functionality to a separate utility component, we can justify using context in this manner (i.e to access parent/ancestor props) — doing this (or something like this) manually for each UI component in your library would just not be worth it.

Conclusion

The aim of this article has been to identify a solution that allows information from an ancestor to be inherited by descendants without polluting our JSX, without coupling components together, and without negatively impacting our Developer Experience. The motivation came from having to repeatedly pass the same information to sibling components when they required access to the same state. Global state managers like Redux were to be avoided. The end solution of using a custom higher-order-component instead of HTML tags allows us to satisfy our goals by allowing us to use functions as children, where we can expose our customised context object containing parent props.

Further Thoughts

I’m working on a library that explores in more depth the concepts discussed in this article, it’s called Lucid and is available on Github.

I’ve also written some other articles that explore similar ideas to the ones discussed in this article, you can check them out here if you like: