All Articles

The root component pattern

If you have used the React material-ui library, you might already be familiar with the root component pattern. For example:

<Grid
  container
  component={MyComponent}
  myProp={"forwarded on to MyComponent"}
>

Here, the component prop on the Grid component accepts an element type (defaulted to div), to render as the root component of the grid. The Grid component forwards any props onto MyComponent.

This pattern effectively extends MyComponent with the functionality provided by Grid. Most of the use cases for setting the root component are presentational, for example, you might want a Paper component that has grid-like properties:

<Paper
  // Paper props
  variant="outlined"
  component={Grid}
  // Grid props
  container
  spacing={1}
/>

Or perhaps you would like the ability to set the underlying stying for a custom provider:

<ToDoProvider
  // ToDo props
  toDos={toDos}
  component={Paper}
  // Paper props
  variant="outline"
>
  // ...Components that need toDos
</ToDoProvider

Let’s dive a little deeper into that last example to explore how we can implement this pattern, starting with a basic provider template:

import React from "react";

const ToDoContext = React.createContext(null);

const ToDoProvider = ({ toDos, children }) => (
  <ToDoContext.Provider value={toDos}>{children}</ToDoContext.Provider>
);

export { ToDoProvider };

Hopefully, you are already familiar with this pattern; we have created a React context, implemented a custom provider which renders the context provider and set the provider value to toDos. Now all children will be able to access the toDos either through a custom hook, or a consumer component.

Next, let’s implement the root component pattern. This pattern hinges on the React.createElement function. Remember that JSX, the code you write between the < >, is just syntactic sugar over the top of React.createElement. For example, if we put the following code through Babel repl

<div className="full-width">
  Hello World!
</div>

then the output is

React.createElement("div", {
  className: "full-width"
}, "Hello World!");

React.createElement has the following signature (taken straight from the React docs):

React.createElement(
  type,
  [props],
  [...children]
)

And we can see that this is the case in our earlier example; the JSX had type div, a single prop called className and only one child, the string "Hello World!".

Now that we know how to use React.createElement we can finish off our component. First, we’ll take the component type as a prop, destructuring any other props the provider needs and collecting the rest into a variable called passThroughProps. We will then return the output of React.createElement, passing our earlier render as children of the element.

import React from "react";

const ToDoContext = React.createContext(null);

const ToDoProvider = ({ toDos, component, children, ...passThroughProps }) =>
  React.createElement(
    component,
    passThroughProps,
    <ToDoContext.Provider value={toDos}>{children}</ToDoContext.Provider>
  );

export { ToDoProvider };

And that’s it. Now we can render any component we like as the root of the ToDoProvider.

<ToDoProvider
  // ToDo props
  toDos={toDos}
  component={Paper}
  // Paper props
  variant="outline"
>
  // ...Components that need toDos
</ToDoProvider

I hope you find this pattern useful, or at least now understand how libraries like material-ui work under the hood.

Thanks for reading!