Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle multiple context within React?

New to React - I am trying to use multiple contexts within my App component, I tried following the official guide on multiple contexts.

Here is my current code:

App.js

import React from "react";
import { render } from "react-dom";
import Login from "./Login";
import AuthContext from "./AuthContext";
import LayoutContext from "./LayoutContext";
import LoadingScreen from "./LoadingScreen";

class App extends React.Component {
  render() {
    const { auth, layout } = this.props;

    return (
      <LayoutContext.Provider value={layout}>
        <LoadingScreen />
        <AuthContext.Provider value={auth}>
          <AuthContext.Consumer>
            {auth => (auth.logged_in ? console.log("logged in") : <Login />)}
          </AuthContext.Consumer>
        </AuthContext.Provider>
      </LayoutContext.Provider>
    );
  }
}

render(<App />, document.getElementById("root"));

Login.js

import React from "react";

class Login extends React.Component {
  render() {
    return (
      <div></div>
    );
  }
}

export default Login;

AuthContext.js

import React from "react";

const AuthContext = React.createContext({
  logged_in: false
});

export default AuthContext;

LayoutContext.js

import React from "react";

const LayoutContext = React.createContext({
  show_loading: false
});

export default LayoutContext;

LoadingScreen.js

import React from "react";
import LayoutContext from "./LayoutContext";

class LoadingScreen extends React.Component {
  render() {
    return (
      <LayoutContext.Consumer>
        {layout =>
          layout.show_loading ? (
            <div id="loading">
              <div id="loading-center">
                <div className="sk-chasing-dots">
                  <div className="sk-child sk-dot1"></div>
                  <div className="sk-child sk-dot2"></div>
                </div>
              </div>
            </div>
          ) : null
        }
      </LayoutContext.Consumer>
    );
  }
}

export default LoadingScreen;

Following the example, I never really understood how this.props (in App.js) could hold my different contexts.

Both auth and layout show up as undefined, this.props is empty, which will in turn cause my app to throw errors such as Cannot read property 'show_loading' of undefined

I immediately liked the example provided in the React documentation, but I can't get this to work.

like image 658
joao Avatar asked Aug 31 '19 19:08

joao


People also ask

Can I use multiple contexts in React?

Consuming Multiple Contexts To keep context re-rendering fast, React needs to make each context consumer a separate node in the tree. If two or more context values are often used together, you might want to consider creating your own render prop component that provides both.

How do you pass multiple values in context React?

To pass multiple values in React Context, we can use the Provider API. Also, we can easily consume the context data by utilizing the useContext React Hook. However, it is important to understand the basic syntax and approach behind this.

Can we have multiple context provider?

Context does NOT have to be global to the whole app, but can be applied to one part of your tree. You can (and probably should) have multiple logically separated contexts in your app.


1 Answers

I've made a small snippet to show you how you could structure your context providers and consumers.

My App component in this case is the root of the app. It has all the providers, along with the value for each one of them. I am not changing this value, but I could if I wanted to.

This then has a single child component, MyOutsideComponent, containing all the chained consumers. There are better ways to do this, I just wanted to show you, one by one, how chaining consumers work. In practice you can neatly reduce this using a few techniques.

This MyOutsideComponent has the actual component, MyComponent, which takes all the context elements and just puts their value on the page. Nothing fancy, the point was to show how the values get passed.

let FirstContext = React.createContext('first');

let SecondContext = React.createContext('second');

let ThirdContext = React.createContext('third');

let FourthContext = React.createContext('fourth');

let MyComponent = (props) => {
  return (<span >{Object.values(props).join(" ")}</span>);
};

let App = (props) => {
  return (
  <FirstContext.Provider value="this is">
    <SecondContext.Provider value="how you">
      <ThirdContext.Provider value="pass context">
        <FourthContext.Provider value="around">
          <MyOutsideComponent />
        </FourthContext.Provider>
      </ThirdContext.Provider>
    </SecondContext.Provider>
  </FirstContext.Provider>
  );
};

let MyOutsideComponent = () => {
    return ( < FirstContext.Consumer >
          {first =>
            (< SecondContext.Consumer >
             {second =>
               (< ThirdContext.Consumer >
                 {third =>
                   (<FourthContext.Consumer >
                     {fourth =>
                      (<MyComponent first={first} second={second} third={third} fourth={fourth} />)
                     }
                   </FourthContext.Consumer>)
                 }
               </ThirdContext.Consumer>)
             }
           </SecondContext.Consumer>)
         }
       </FirstContext.Consumer>);
   }
   
ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="app"></div>

Now, for the actual explanation. createContext gives you two actual components: a Provider and Consumer. This Provider, as you found out, has the value. The Consumer takes as child a single function taking one argument, which is your context's value.

This is where the docs are a bit unclear, and a bit which I hope I can help a bit. This does not get passed automatically in props unless the Provider is the direct parent of the component. You have to do it yourself. So, in the example above, I chained four consumers and then lined them all up in the props of my component.

You've asked about class-based components, this is how it ends up looking like:

let FirstContext = React.createContext('first');

let SecondContext = React.createContext('second');

let ThirdContext = React.createContext('third');

let FourthContext = React.createContext('fourth');

class MyComponent extends React.Component {
  render() {
    return ( < span > {Object.values(this.props).join(" ")} < /span>);
  }
}

class App extends React.Component {
  render() {
    return (
      <FirstContext.Provider value = "this is" >
        <SecondContext.Provider value = "how you" >
          <ThirdContext.Provider value = "pass context" >
            <FourthContext.Provider value = "around" >
              <MyOutsideComponent / >
            </FourthContext.Provider>
          </ThirdContext.Provider >
        </SecondContext.Provider>
      </FirstContext.Provider >
    );
  }
}

class MyOutsideComponent extends React.Component {
  render() {
    return (
      <FirstContext.Consumer >
        { first => 
          (< SecondContext.Consumer >
            { second =>
              ( < ThirdContext.Consumer >
                {  third =>
                  ( < FourthContext.Consumer >
                    { fourth =>
                      ( < MyComponent first = {first} second={second} third={third} fourth={fourth} />)
                    }
                  </FourthContext.Consumer>)
                }
              </ThirdContext.Consumer>)
            }
          </SecondContext.Consumer>)
        }
      </FirstContext.Consumer>
    );
  }
}
ReactDOM.render( < App / > , document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="app" />
like image 112
Sébastien Renauld Avatar answered Oct 18 '22 00:10

Sébastien Renauld