tl;dr to fix "wrapper hell" while using multiple instance of React Context API (16.3+)
You have to admit that the React Context API is an extremely useful, if you need to get props from parent component to child component, and between them is a whole universe of nested things. But this advantage quickly disappears when you need to use more than one context at the same component level.
Click here and you will see the problem I'm talking about
import React from 'react';
import { ContextA, ContextB, ContextC } from './contexts';
export class ComponentProvider extends React.Component {
render() {
return (
<ContextA.Provider value={this.props.list}>
<ContextB.Provider value={this.props.config}>
<ContextC.Provider value={this.props.theme}>
{this.props.children}
</ContextC.Provider>
</ContextB.Provider>
</ContextA.Provider>
)
}
}
import React from 'react';
import { ContextA, ContextB, ContextC } from './contexts';
export class ComponentConsumer extends React.Component {
render() {
return (
<ContextA.Consumer>
{list => (
<ContextB.Consumer>
{config => (
<ContextC.Consumer>
{themeClass => (
<React.Fragment>
<div className={themeClass}>
<ol>
{list.map(i => (
<li key={i}>{i}</li>
))}
</ol>
<ul>
{Object.entries(config).map(([k, v]) => (
<li key={k}>{`${k}: ${v}`}</li>
))}
</ul>
</div>
</React.Fragment>
)}
</ContextC.Consumer>
)}
</ContextB.Consumer>
)}
</ContextA.Consumer>
)
}
}
The package provides you a couple of simple HOCs:
withContextProvider
andwithContextConsumer
. Each HOC returns a call tree of React.Provider or React.Consumer, depending on what you want to receive. That call tree wrapped around your component. And finally, your component can now use the React Context API via props.
$ npm install react-with-multiple-contexts
Or if you prefer using Yarn:
$ yarn add react-with-multiple-contexts
callback
is a function that has the props from the Component as an argument and returns a new object that must contain the context and the value that goes into the context.
contexts
is an object where each property name is the name that you can use in your component through the props and get property value, which is a context value in your component.
// componentProvider.jsx
import React from 'react';
import { withContextProvider } from 'react-with-multiple-contexts';
import { contextA, contextB, contextC } from './contexts';
const DummyComponent = (props) => (
// props also has everything that pass through the context
// such as props.list, props.config and props.theme
<React.Fragment>{props.children}</React.Fragment>
);
export const ComponentProvider = withContextProvider(DummyComponent, (props) => ([
// where each context(A|B|C) it's just an empty React.createContext(null)
{ context: contextA, value: props.list },
{ context: contextB, value: props.config },
{ context: contextC, value: props.theme }
]));
// componentConsumer.jsx
import React from 'react';
import { withContextConsumer } from 'react-with-multiple-contexts';
import { contextA, contextB, contextC } from './contexts';
const DummyComponent = ({ themeClass, list, config }) => (
<div className={themeClass}>
<ol>
{list.map(i => (
<li key={i}>{i}</li>
))}
</ol>
<ul>
{Object.entries(config).map(([k, v]) => (
<li key={k}>{`${k}: ${v}`}</li>
))}
</ul>
</div>
};
export const ComponentConsumer = withContextConsumer(DummyComponent, {
list: contextA,
config: contextB,
themeClass: contextC
});
// app.jsx
import React from 'react';
import { ComponentProvider } from './componentProvider';
import { ComponentConsumer } from './componentConsumer';
const App = () => {
// Technically you can use state here
// Just pass it to Provider like props
const justProps = {
list: [1,2,3],
config: {
cool: true,
boring: false
},
theme: 'dark',
};
return (
<ComponentProvider {...justProps}>
<div className="app">
<div className="child">
{/*
Consumer below receives everything
from Provider's props via React.Context API
*/}
<ComponentConsumer />
</div>
</div>
</ComponentProvider>
);
}
As you know, hooks are an upcoming feature that lets you use state and other React features without writing a class. useContext
is one of the hooks, and that hook can simplify consuming value from Context.Consumer with just const context = useContext(Context);
. This is even easier than API from this package. But since it is a hook, you can use useContext
only in functional components, and still, you need to write nested providers. Although you can easily write something like useProvider(Component, value)
, but I'm not sure about that yet.
P.S. those HOCs also support Forwarding Refs (16.3+)
MIT © Artem Anikeev