Customizing React components using a components provider
When we talk about component customization, we usually think of style theming: changing the way a component looks by adjusting its style properties, based on some global values (the theme).
In this story, however, I’ll be talking about a different level of customization. When you develop a UI library, some of the more complex components are created using other basic components (think a ConfirmationDialog
using a Button
). Sometimes letting the user change the style of the basic components deeply nested inside the more complex one is cumbersome; sometimes is just not enough: what if the user want’s to totally change the way a Button
renders inside a ConfirmationDialog
?
Providing components
You can use React’s Context functionality to create a provider of components for your library. This provider will act as a registry, where you associate each basic component with its implementation. You can then get those basic components from the registry, and use them to build the rest of the components. That way, you can change the look & feel or the behavior of a basic component, even when it is deeply nested inside other components.
A simple components provider
The following code snippet shows a simple components provider:
You can use the provider in your app like this:
The Page
component in the previous code snippet uses a custom useComponents
hook to get some components from the provider. In this case there is no ComponentsProvider
around the Page
component, so it will use the default Button
and Icon
.
You can add aComponentsProvider
to the App
and change the provided components (line 23). This in turn will change the components used by Page
:
Cherry-pick component overrides
In the previous examples you had to provide all the components every time you choose to use ComponentsProvider
. Of course, that is not the best of the APIs. You can use spreading to let the user override just some of the default components:
Now you can override the Icon
component and use the default Button
:
Meta-provided components
You can provide components that in turn use other provided components to render themselves. The provided Button
in the previous examples could be defined as:
We’re creating a cyclic dependency here (components-provider.jsx
depends on button.jsx
to provide the default Button
implementation, which in turn depends on components-provider.jsx
to get the Icon
implementation), but thanks to the way exports work in ES, the module system has no problem resolving the dependencies at build time.
Using a provider like this, you can create libraries of components that are customizable beyond its styles, letting your users change the way basic components look, and also how they behave.
In this story I’ve shown you a naïve implementation of the provider, to make the core idea easier to grasp. In future stories we’ll see how to create a provider that you could use in real applications. Stay tuned!