Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reduce react context hell?

I have inherited a codebase where the previous owner has made extensive use of React.Context. This has resulted in what might be described as "context hell"

<AppContextProvider>
  <AnotherProvider>
    <AgainAnotherProvider configProp={false}>
      <TestProvider>
        <FooProvider>
          <BarProvider configHereAlso={someEnvronmentVar}>
            <BazProvider>
              <BatProvider>
                <App />
              </BatProvider>
            </BazProvider>
          </BarProvider>
        </FooProvider>
      </TestProvider>
    </AgainAnotherProvider>
  </AnotherProvider>
</AppContextProvider>;

This feels like an anti-pattern and is causing considerable cognitive overhead in understanding how the whole application works.

How do I fix this? Is it possible to use just one provider for everything? I previously used redux-toolkit with redux for managing state in react. Is there anything similar for contexts?

like image 983
spinners Avatar asked May 10 '21 09:05

spinners


2 Answers

I found an elegant solution:

const Providers = ({providers, children}) => {
  const renderProvider = (providers, children) => {
    const [provider, ...restProviders] = providers;
    
    if (provider) {
      return React.cloneElement(
        provider,
        null,
        renderProvider(restProviders, children)
      )
    }

    return children;
  }

  return renderProvider(providers, children)
}

ReactDOM.render(
  <Providers providers={[
    <FooContext.Provider value="foo" />,
    <BarContext.Provider value="bar" />,
    <BazContext.Provider value="baz" />,
  ]}>
    <App />
  </Providers>,
  document.getElementById('root')
);
like image 135
Dinislam Maushov Avatar answered Oct 07 '22 03:10

Dinislam Maushov


One way to get rid of additional contexts is by adding more variables to the value that is provided by a context.

For example if there are two contexts UserContext and ProfileContext with providers like:

<UserContext.Provider value={user}... and

<ProfileContext.Provider value={profile}...

then you can merge them into a single context:

<UserProfileContext.Provider value={{user, profile}}...

Note: This doesn't mean you should merge all contexts into a single context because of the separation of concerns and all consumers rerender when a context's value changes leading to unwanted renders.

like image 33
Ramesh Reddy Avatar answered Oct 07 '22 03:10

Ramesh Reddy