1

I'm trying to learn the Context API, and what I want to achieve, is showing the button Login and Sign up on the navigation but I had another error that had from another post I read the docs but me reading and not doing visually that how I learned by doing it making mistaking.

The buttons should open up two modal windows one login and sign up form.

Modal.js

import React from 'react';
import ReactDOM from "react-dom";
import Modal from "react-modal";
import ModalContext from '../Forms/ModalContext';


class ModalProvider extends React.Component {
  state = {
    loginOpened: false,
    signupOpened: false
  };

  openModal = modalType => () => {
    if (modalType === "login") {
      this.setState({
        loginOpened: true,
        signupOpened: false
      });
    } else if (modalType === "signup") {
      this.setState({
        loginOpened: false,
        signupOpened: true
      });
    }
  };

  closeModal = modalType => () => {
    if (modalType === "login") {
      this.setState({
        loginOpened: false
      });
    } else if (modalType === "signup") {
      this.setState({
        signupOpened: false
      });
    }
  };

  render(props) {

    return (
      <ModalContext.Provider value={{openModal: this.openModal, closeModal: this.closeModal}}>
        <Modal isOpen={loginOpened} onRequestClose={this.closeModal("login")}>
          <h1>Login</h1>
          <button onClick={this.openModal("signup")}>Open Signup</button>
          <button onClick={this.closeModal("login")}>Close this modal</button>
        </Modal>
        <Modal isOpen={signupOpened} onRequestClose={this.closeModal("signup")}>
           <h1>Sign Up</h1>
           <button onClick={this.openModal("login")}>Open Login</button>
           <button onClick={this.closeModal("signup")}>Close this modal</button>
        </Modal>
        {props.children}
      </ModalContext.Provider>
    )
  }
}

export default ModalProvider

ModalContext.js

I don't know why the person that helped me and explain did a very great job explain to but just want to know why it is just this line of code.

import {createContext} from 'react'

export default createContext()

Navigation.js

import React from 'react';
import { BrowserRouter as Router, Link } from 'react-router-dom';
import Dropdown from "../dropdowns/dropdowns"; 
import hamburger from "../images/menu.svg"
// This will display the login and sign up buttons
import ModalContext from '../Forms/ModalContext';

class Navigation extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            isExpanded: false
        };
    }

    handleToggle(e) {
        e.preventDefault();
        this.setState(prevState => ({
          isExpanded: !prevState.isExpanded, // negate the previous expanded state
        }));
      }


    render(props) {

        const { isExpanded } = this.state;

      return (
          <Router>
              <div className="NavbarContainer main">
                  <div className="mobilecontainer LeftNav">
                      <h2 className="BrandName LeftNav mobileboxmenu inline FarRight">Kommonplaces</h2>
                      <div className="hamburger inlinev" >
                          <img 
                            onClick={e => this.handleToggle(e)}
                            alt="menubtn" 
                            src={hamburger}
                          />
                      </div>
                  </div>

                  <div className={`NavBar collapsed ${isExpanded ? "is-expanded" : ""}`}>
                      <div className="col-a">
                        <Dropdown/>    
                        <li className="RightNav"><Link to="/">Host Your Space</Link></li>
                        <li className="RightNav"><Link to="/">About Us</Link></li>
                        <li className="RightNav"><Link to="/">Contact Us</Link></li>
                      </div>

                      <div className="col-c">
                        { /* 4. call the prop functions in `Navigation` component */ }

                        <ModalContext.Consumer>
                          {({openModal, closeModal}) => <button onClick={openModal("login")}>Login</button>}
                          {({openModal, closeModal}) => <button onClick={openModal('signup')}>Sign Up</button>}
                        </ModalContext.Consumer>
                      </div>
                  </div>
               </div>
         </Router>
      );
    }
}

  export default Navigation;

3
  • I think you should not write render(props) props in render Commented Mar 12, 2020 at 19:28
  • You want to use context because modal is not a child of navigation menu but you'd like navigation menu and modal to manipulate a value that opens or closes a modal? Commented Mar 12, 2020 at 19:31
  • @HMR Yes, I want the navigation menu and modal to intertwined with each other. Commented Mar 12, 2020 at 19:36

2 Answers 2

1

So you first created the ModalContext and the context gives you a Provider and a Consumer.

If you want to use the context for a Consumer, there should be a Provider providing it. For that to happen, the Consumer should be a child of the Provider so that it has access to it.

<Provider>
    ....
    ...
    <Consumer />
    ...
</Provider>

But in your case, Provider is not a ancestor of the Consumer.

Typically this is how this gets played out.

Navigation.js

import React from "react";
import { BrowserRouter as Router, Link } from "react-router-dom";
import Dropdown from "../dropdowns/dropdowns";
import hamburger from "../images/menu.svg";
// This will display the login and sign up buttons
import ModalContext from "../Forms/ModalContext";
import ModalProvider from "../Forms/ModalProvider";

class Navigation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isExpanded: false
    };
  }

  handleToggle(e) {
    e.preventDefault();
    this.setState(prevState => ({
      isExpanded: !prevState.isExpanded // negate the previous expanded state
    }));
  }

  render(props) {
    const { isExpanded } = this.state;

    return (
      <Router>
        {/* Modal provider provides the context to all the children */}
        <ModalProvider>
          <div className="NavbarContainer main">
            <div className="mobilecontainer LeftNav">
              <h2 className="BrandName LeftNav mobileboxmenu inline FarRight">
                Kommonplaces
              </h2>
              <div className="hamburger inlinev">
                <img
                  onClick={e => this.handleToggle(e)}
                  alt="menubtn"
                  src={hamburger}
                />
              </div>
            </div>

            <div
              className={`NavBar collapsed ${isExpanded ? "is-expanded" : ""}`}
            >
              <div className="col-a">
                <Dropdown />
                <li className="RightNav">
                  <Link to="/">Host Your Space</Link>
                </li>
                <li className="RightNav">
                  <Link to="/">About Us</Link>
                </li>
                <li className="RightNav">
                  <Link to="/">Contact Us</Link>
                </li>
              </div>

              <div className="col-c">
                {/* 4. call the prop functions in `Navigation` component */}

                {/* Consumer has access to context as children as function*/}
                <ModalContext.Consumer>
                  {({openModal, closeModal, loginOpened, signupOpened}) => {
                    return (
                      <React.Fragment>
                        <button onClick={openModal("login")}> Login</button>
                        <button onClick={openModal("signup")}>Sign Up</button>
                        <Modal
                          isOpen={loginOpened}
                          onRequestClose={closeModal("login")}
                        >
                          <h1>Login</h1>
                          <button onClick={openModal("signup")}>
                            Open Signup
                          </button>
                          <button onClick={closeModal("login")}>
                            Close this modal
                          </button>
                        </Modal>
                        <Modal
                          isOpen={signupOpened}
                          onRequestClose={closeModal("signup")}
                        >
                          <h1>Sign Up</h1>
                          <button onClick={openModal("login")}>
                            Open Login
                          </button>
                          <button onClick={closeModal("signup")}>
                            Close this modal
                          </button>
                        </Modal>
                      </React.Fragment>
                    );
                  }}
                </ModalContext.Consumer>
              </div>
            </div>
          </div>
        </ModalProvider>
      </Router>
    );
  }
}

export default Navigation;

ModalProvider.js

import React from "react";
import ReactDOM from "react-dom";
import Modal from "react-modal";
import ModalContext from "../Forms/ModalContext";

class ModalProvider extends React.Component {
  state = {
    loginOpened: false,
    signupOpened: false
  };

  openModal = modalType => () => {
    if (modalType === "login") {
      this.setState({
        loginOpened: true,
        signupOpened: false
      });
    } else if (modalType === "signup") {
      this.setState({
        loginOpened: false,
        signupOpened: true
      });
    }
  };

  closeModal = modalType => () => {
    if (modalType === "login") {
      this.setState({
        loginOpened: false
      });
    } else if (modalType === "signup") {
      this.setState({
        signupOpened: false
      });
    }
  };

  render(props) {
    return (
      <ModalContext.Provider
        value={{
          openModal: this.openModal,
          closeModal: this.closeModal,
          signupOpened: this.state.signupOpened,
          loginOpened: this.state.loginOpened,
        }}
      >
        {props.children}
      </ModalContext.Provider>
    );
  }
}

export default ModalProvider;
Sign up to request clarification or add additional context in comments.

6 Comments

So, for Provider do I have to install like an npm package to
Not really, you just have to include that in your component tree. Check updated code snippet.
Thank you so much for explaining it for me @Sushanth --
Thank you, so let explain now see if understand clear ModalProvider.js is the one that will allow the navigation to intercept with Modal Window and inside of ModalProvider.js we calling props to called the file Navigation.js right @Sushanth
ModalProvider will just provide the context object ( in this case the behavior of the modal ). So which ever consumer ( Modal, Button in this case ) accesses it will have a handle of the context object exposed by the Provider. It is exactly the same except that you don't have to keep passing the props to all the children in the tree until it reaches the comps that use them
|
0

I was writing an example app as well so will post it as an answer:

function ToggleModal({ checked, onChange, modalId }) {
  console.log('rendering:', modalId);
  return (
    <label>
      {modalId}
      <input
        type="checkbox"
        checked={checked}
        onChange={onChange}
      />
    </label>
  );
}
const ToggleModalContainer = ({ modalId }) => {
  const { modals, changeModal } = React.useContext(State);
  const checked = modals[modalId];
  return React.useMemo(
    () =>
      ToggleModal({
        checked,
        modalId,
        onChange: () => changeModal(modalId, !checked),
      }),
    [changeModal, checked, modalId]
  );
};
function Modals() {
  const state = React.useContext(State);
  return Object.entries(state.modals).map(
    ([key, value]) =>
      value && <div key={key}>this is modal {key}</div>
  );
}

const State = React.createContext();
const App = () => {
  const [modals, setModals] = React.useState({
    a: false,
    b: false,
    c: false,
  });
  const changeModal = React.useCallback(
    (modalId, open) =>
      setModals(modals => ({ ...modals, [modalId]: open })),
    []
  );
  const state = React.useMemo(
    () => ({
      modals,
      changeModal,
    }),
    [changeModal, modals]
  );

  return (
    <State.Provider value={state}>
      <React.Fragment>
        {Object.keys(modals).map(modalId => (
          <ToggleModalContainer
            modalId={modalId}
            key={modalId}
          />
        ))}
        <Modals />
      </React.Fragment>
    </State.Provider>
  );
};

//render app
ReactDOM.render(
  <App />,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Comments

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.