facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
227.66k stars 46.45k forks source link

How to combo multiple ContextProvider #14520

Closed hangyangws closed 5 years ago

hangyangws commented 5 years ago

I learn the Hooks,it's so good! Bug when I use hooks: useReducer。I confused by multiple provider.

my simple files

  1. app.js:

    import Provider from './store/index.js';
    
    ReactDOM.render(
    <Provider>
      <Router history={history}>
        <Layout>
          <Switch>
            <Route ... />
          </Switch>
        </Layout>
      </Router>
    </Provider>,
    CONTAINER
    );
  2. ./store/index.js

    import providerOne from './providerOne.js';
    import ProviderTwo from './providerTwo.js';
    import ProviderThree from './providerThree.js';
    // there maybe have many providers...
    
    return (
    <ProviderOne>
      <ProviderTwo>
        <ProviderThree>
          {props.children}
        </ProviderThree>
      </ProviderTwo>
    </ProviderOne>
    );
  3. ./store/providerOne.js

    const Context = React.createContext();
    
    const initState = [];
    const reducer = (state, action) => {
    switch (action.type) {
      case 'xxx':
        return xxx;
      default:
        return state;
    }
    };
    
    const ProviderOne = (props) => {
    const [state, dispatch] = React.useReducer(reducer, initState);
    
    return <Context.Provider value={{ state, dispatch }}>{props.children}</Context.Provider>;
    };
    
    export default ProviderOne;

my confusion

In file ./store/index.js, I must write many times <Provider> structure. There is any way to solve it.

tmenyhart commented 5 years ago

Hey @hangyangws , I was just thinking about the same problem, and I've found the solution for this in this repo: https://github.com/FormidableLabs/react-context-composer

This solution was fine for me, hope it helps you too.

Simply put, if you want to get rid of the deep nesting of context providers you can write a util functional component like this:

export const ContextProviderComposer = ({contextProviders, children}) => {
  return contextProviders.reduceRight((children, parent, index) => React.cloneElement(parent, { children }), children);
};

ContextProviderComposer.propTypes = {
  contextProviders: PropTypes.arrayOf(
    PropTypes.element,
  ).isRequired,
  children: PropTypes.node.isRequired,
};

And you can use it in a component by listing your context providers:

import providerOne from './providerOne.js';
import ProviderTwo from './providerTwo.js';
import ProviderThree from './providerThree.js';

return (
  <ContextProviderComposer contextProviders={[
    <ProviderOne key={0}/>
    <ProviderTwo key={1}/>
    <ProviderThree key={2}/>
  ]}>
    { props.children }
  </ContextProviderComposer>
);
aweary commented 5 years ago

We use the issue tracker for bug reports and feature requests.

If you have a question, please check our community support resources: https://facebook.github.io/react/community/support.html

csr632 commented 4 years ago

I have created another state management library that is better at service composition. Here is a demo of avoiding provider hell. Feel free to try it or read its source(100 lines of code)!

nanxiaobei commented 3 years ago

react-easy-contexts, A simple tool to add multiple React contexts easily.

https://github.com/nanxiaobei/react-easy-contexts

import { create, useProvider } from 'react-easy-contexts';

const ctx = create({
  useX() {
    const [x, setX] = useState(0);
    return useMemo(() => ({ x, setX }), [x]);
  },
  useY() {
    const [y, setY] = useState(0);
    return useMemo(() => ({ y, setY }), [y]);
  },
  useZ() {
    const [z, setZ] = useState(0);
    return useMemo(() => ({ z, setZ }), [z]);
  },
});

const App = () => {
  const Provider = useProvider(ctx);
  return (
    <Provider>
      <AppMain />
    </Provider>
  );
};