6

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.

2
  • How are you passing props to your App component? Commented Aug 31, 2019 at 19:22
  • I don't really know! The react docs don't mention much about props on the context documentation. Commented Aug 31, 2019 at 19:38

1 Answer 1

17

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" />

Sign up to request clarification or add additional context in comments.

2 Comments

This is really helpful! Thanks for writing all of that. Is there any way to pass props to App in a class based component?
@joao Added a second, entirely class-based example for you :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.