The React Context hell
Tags: react, javascript, typescript, showdev
Published: 2021-04-21 16:43:36

## What is the React Context hell?

Like the [callback hell](http://callbackhell.com/), usual when [jQuery](https://jquery.com/) was used for everything, the __React Context hell__ is the nasty code you get taking advantage of the [React Context](https://it.reactjs.org/docs/context.html) API.

```jsx
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](https://it.reactjs.org/docs/context.html) 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](https://reactjs.org/docs/react-api.html#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. 

```typescript
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.

```typescript
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`.

```jsx
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](https://github.com/alfredosalzillo/react-pendulum) library.

{% github https://github.com/alfredosalzillo/react-pendulum %}