What is the React Context hell?
Like the callback hell, usual when jQuery was used for everything, the React Context hell is the nasty code you get taking advantage of the React Context API.
const App = () => {
// ... some code
return (
<>
<ReduxProvider value={store}>
<ThemeProvider value={theme}>
<OtherProvider value={otherValue}>
<OtherOtherProvider value={otherOtherValue}>
{/** ... other providers*/}
<HellProvider value={hell}>
<HelloWorld />
</HellProvider>
{/** ... other providers*/}
</OtherOtherProvider>
</OtherProvider>
</ThemeProvider>
</ReduxProvider>
</>
)
}
How to fix it?
To clean up the nasty code you get from taking advantage of React Context API we need a way to nest multiple Context.Provider
without passing them as children
of each other.
To achieve that we can use the React.cloneElement API.
The cloneElement
API
React.cloneElement(
element,
[props],
[...children]
)
Clone and return a new React element using element as the starting point. The resulting element will have the original elementโs props with the new props merged in shallowly. New children will replace existing children. key and ref from the original element will be preserved.
We can use the cloneElement
API to reduce
a collection of providers, this way we don't have to nest them inside each other.
return [
<ReduxProvider value={store} />,
<ThemeProvider value={theme} />,
<OtherProvider value={otherValue} />,
<OtherOtherProvider value={otherOtherValue} />,
// ...others,
<HellProvider value={hell} />,
<HelloWorld />,
].reduceRight((prev, provider) => React.cloneElement(provider, {}, prev))
The last element of the array is the content of the app.
Using reduceRight
we preserve the nesting to make the HelloWorld
element a child of all the providers.
To make it simpler to use we can implement a MultiProvider
component.
import React from 'react'
const nest = (
children: React.ReactNode,
component: React.ReactElement
) => React.cloneElement(component, {}, children)
export type MultiProviderProps = React.PropsWithChildren<{
providers: React.ReactElement[]
}>
const MultiProvider: React.FC<MultiProviderProps> = ({
children,
providers
}) => (
<React.Fragment>
{providers.reduceRight(nest, children)}
</React.Fragment>
)
export default MultiProvider
Now we can refactor the example using the MultiProvider
.
const App = () => {
return (
<MultiProvider
providers={[
<ReduxProvider value={store} />,
<ThemeProvider value={theme} />,
<OtherProvider value={otherValue} />,
<OtherOtherProvider value={otherOtherValue} />,
// ...others,
<HellProvider value={hell} />,
]}
>
<HelloWorld />
</MultiProvider>
)
}
You can find an implementation of MultiProvider
inside the react-pendulum library.
alfredosalzillo / react-pendulum
A React Context utility library.
react-pendulum
A React Context utility library.
Install
Using npm
npm install --save react-pendulum
Using yarn
yarn add react-pendulum
Components
MultiProvider
A component to clean up the nasty code you get from taking advantage of React Context API.
Props
-
providers
the array of providers instances to wrap to thechildren
import React, { Component, createContext } from 'react'
import { MultiProvider } from 'react-pendulum'
const FirstNameContext = createContext<string>('John')
const LastNameContext = createContext<string>('Doe')
const HelloWorld = () => {
const firstName = useContext(FirstNameContext)
const lastName = useContext(LastNameContext)
return <>{`Hello ${firstName} ${lastName}`}</>
}
class App extends Component {
render() {
return (
<MultiProvider
providers={[
<FirstNameContext.Provider value='Yugi' />
โฆ
Top comments (19)
Exactly. No point digging into internals when you can just import.
This looks way cleaner IMO. Keep it simple and easy to read.
Agree.
Why is it better?
I am wondering this to. It seems like unnecessary complexity and abstraction that makes the code just look messier.
My last article is on this topic dev.to/ivanjeremic/to-use-context-...
Good point to alert context hell!
I simple skip use context, instead works with simple useReducer, and pass down actions and state in props. That way give more simple component, because my component don't have outer dependency - expect props, so easy join together in quite complex app.
You could just use a real state management library instead of context.
For example?
Recoil?
So you don't use react router and have a BrowserRouter in your code? Or if you use CSS in JS solution, you don't have a kind of ThemeProvider? Or you put both in a recoil state?
Ah and you still need the RecoilRoot, a provider.
I could see what you mean, but no we use riot router, donโt do css in js, and use cerebral js, so we have maybe one provider nested out app.
Having a bit too many react contexts was very relatable! Didn't even know the
React.cloneElement API
till now! Thank you!I think I made an even simpler one here - dev.to/jdgamble555/freakin-easy-re..., let me know your thoughts
I wish React could have something like Services of Angular.
You can use mobx if you want some spaghetti ๐ in your project.
That's pretty neat ๐ค
KISS advocates: ๐๐๐
Some comments may only be visible to logged-in visitors. Sign in to view all comments.