Creating Modals/Popups with useContext

Creating Modals/Popups with useContext

Modals can be enabled in React by using the 'React createPortal', but for some reason, I was unable to do the same in NextJs. I made the decision to look for a solution. I discovered a technique with "useContext" that avoided the need for "createPortal." hometree.png You can see that the "SignupForm" and the "SuccessModal" are direct children of the "Home" in the tree-like image above. The "Button" component of the signup form is a direct child of the "SignupForm".

I initiated a "usestate" in the "Home" and then pass its "setState"(this sets the state to true once it is called) to the "SignupForm" before passing the truthy "setState" to the signup "Button". The state and its falsy setState(this sets the state to false) is passed to the "SuccessModal". The "SuccessModal" gets displayed when the state is true; however, it hides after you click on a close button to set the state value to false.

For a deeper child component, passing the "setState" prop repeatedly until it reaches the "Button" would be tedious. What if a file existed that you could import to any child component you wanted to use the "Usestates" in? This is where useContext comes in.

"UseContext" is a way to handle prop drilling (passing props through nested child components).

So let's get started.

I'm assuming that you are familiar with the useState hook and React. Although I am using a NextJs environment, I can guarantee that the only difference here, is in the file structure.

Open your terminal and run the commands one after the other.

git clone https://github.com/victorgbonna/hashnode

cd .\hashnode\

npm i

git fetch origin

git checkout "day1.5-useContextModal"

npm run dev

A signup form is being displayed. The signup triggers the pop-up button. Everything works fine with the prop drilling, but it will not be tidy on a deeper "Button" component.

Applying useContext

Let us comment-out all the states and functions. So replace what is in your /src/pages/index.js file with this. import { useEffect, useContext, useState} from "react";

export default function Home() {  
  const [showModal, setShowModal]= useState(false)      
  return (
    <main>
      <SignupForm/>
      <SuccessModal/>
    </main>
  );
}


function SignupForm() {  
  return (
  <section>
    <h4>Reg form</h4>
    <form>
      {['username', 'password', 'email'].map((name,ind)=>
        <input type="text" key={ind} placeholder={name}/>
      )}
    </form>
    <Button text="Signup"/>
  </section>
  );
}
function Button({text}) {
  return (
    <button onClick={setShowModalTrue}>{text}</button>
  )
}
function SuccessModal() {     
  if (!showModal) return null;
    return (
    <div
      className='navcont'
      style={{ padding: "0 30%", paddingTop: "10%" }}
    >
        <div className='mainnav'>
          <div
            style={{
              display: "flex",
              flexDirection: "column",
              alignItems: "center",
            }}
          >
          <svg style={{ alignSelf: "flex-end" }}
              className='close'
              onClick={setShowModalFalse} width="24" height="28" viewBox="0 0 24 28" fill="none" xmlns="http://www.w3.org/2000/svg">
            <g id="Close">
              <path id="Oval" fillRule="evenodd" clipRule="evenodd" d="M12 27.6217C18.6274 27.6217 24 21.4747 24 13.8919C24 6.30916 18.6274 0.162109 12 0.162109C5.37258 0.162109 0 6.30916 0 13.8919C0 21.4747 5.37258 27.6217 12 27.6217Z" fill="#F5F8FA"/>
              <g id="ic/close">
                <path id="icon/navigation/close" opacity="0.5" fillRule="evenodd" clipRule="evenodd" d="M15.1501 10.2937C15.0567 10.1865 14.9299 10.1263 14.7976 10.1263C14.6653 10.1263 14.5385 10.1865 14.4451 10.2937L12.0001 13.0854L9.55511 10.2879C9.46169 10.1808 9.33487 10.1206 9.20261 10.1206C9.07035 10.1206 8.94352 10.1808 8.85011 10.2879C8.65511 10.511 8.65511 10.8714 8.85011 11.0946L11.2951 13.892L8.85011 16.6895C8.65511 16.9126 8.65511 17.273 8.85011 17.4961C9.04511 17.7192 9.36011 17.7192 9.55511 17.4961L12.0001 14.6986L14.4451 17.4961C14.6401 17.7192 14.9551 17.7192 15.1501 17.4961C15.3451 17.273 15.3451 16.9126 15.1501 16.6895L12.7051 13.892L15.1501 11.0946C15.3401 10.8772 15.3401 10.511 15.1501 10.2937Z" fill="#AEB0C1"/>
              </g>
            </g>
          </svg>
            <h6 className='suc'>Great</h6>
            <p className='success'>
              You have registered succesfilly
            </p>
          </div>
        </div>
    </div>
  );

}
  1. Create a "hooks" folder. Create a "successContext.js" file inside it.

Let us create the initial state of the states in the "successContext.js"

import {createContext, useState} from 'react'
export const SuccessContext = createContext()

export default function SuccessContextComponent(
    {children}){ 
    const [showModal, setShowModal]= useState(false)                                   

    const setShowModalTrue=()=>{
        setShowModal(true)
    }
    const setShowModalFalse=()=>{
        setShowModal(false)
    }
    return(
        <SuccessContext.Provider value={{
            showModal, setShowModalFalse, setShowModalTrue
        }}>
            {children}
        </SuccessContext.Provider>
    )
}

Terminologies

  • SuccessContext - This holds the context created.
  • hasSignedUp - The state holding the modal. Modal displays when its setState is set to true.
  • SuccessContextComponent - The component holding the context value. Any parent component that imports the SuccessContextComponent, and its child components - even nested too - will get to inherit the hasSignedUp and setHasSignedUp props. Take note of the "value" prop.

    1. Replace the "Home" section with this
import { useEffect, useContext, useState} from "react";
import SuccessContextComponent from "../context/successContext";

export default function Home() {  
  return (
    <SuccessContextComponent>
    <main>
      <SignupForm/>
      <SuccessModal/>
    </main>
    </SuccessContextComponent>
  );
}

You can see where the SuccessContextComponent is being used. Any child component of this file will get access to the "value" passed by it.

  1. Replace the "Button" section with this
function Button({text}) {
  const {setShowModalTrue}=useContext(SuccessContext)
  return (
    <button onClick={setShowModalTrue}>{text}</button>
  )
}

You can see where the SuccessContext is being used. The SuccessContext holds the current state of all that was passed as that "value" props.

  1. Replace the "SuccessModal" section with this
function SuccessModal() {     
  const {showModal, setShowModalFalse}=useContext(SuccessContext)

  if (!showModal) return null;
    return (
    <div
      className='navcont'
      style={{ padding: "0 30%", paddingTop: "10%" }}
    >
        <div className='mainnav'>
          <div
            style={{
              display: "flex",
              flexDirection: "column",
              alignItems: "center",
            }}
          >
          <svg style={{ alignSelf: "flex-end" }}
              className='close'
              onClick={setShowModalFalse} width="24" height="28" viewBox="0 0 24 28" fill="none" xmlns="http://www.w3.org/2000/svg">
            <g id="Close">
              <path id="Oval" fillRule="evenodd" clipRule="evenodd" d="M12 27.6217C18.6274 27.6217 24 21.4747 24 13.8919C24 6.30916 18.6274 0.162109 12 0.162109C5.37258 0.162109 0 6.30916 0 13.8919C0 21.4747 5.37258 27.6217 12 27.6217Z" fill="#F5F8FA"/>
              <g id="ic/close">
                <path id="icon/navigation/close" opacity="0.5" fillRule="evenodd" clipRule="evenodd" d="M15.1501 10.2937C15.0567 10.1865 14.9299 10.1263 14.7976 10.1263C14.6653 10.1263 14.5385 10.1865 14.4451 10.2937L12.0001 13.0854L9.55511 10.2879C9.46169 10.1808 9.33487 10.1206 9.20261 10.1206C9.07035 10.1206 8.94352 10.1808 8.85011 10.2879C8.65511 10.511 8.65511 10.8714 8.85011 11.0946L11.2951 13.892L8.85011 16.6895C8.65511 16.9126 8.65511 17.273 8.85011 17.4961C9.04511 17.7192 9.36011 17.7192 9.55511 17.4961L12.0001 14.6986L14.4451 17.4961C14.6401 17.7192 14.9551 17.7192 15.1501 17.4961C15.3451 17.273 15.3451 16.9126 15.1501 16.6895L12.7051 13.892L15.1501 11.0946C15.3401 10.8772 15.3401 10.511 15.1501 10.2937Z" fill="#AEB0C1"/>
              </g>
            </g>
          </svg>
            <h6 className='suc'>Great</h6>
            <p className='success'>
              You have registered succesfilly
            </p>
          </div>
        </div>
    </div>
  );

}

Note that:

  • The parent code must be wrapped under the useContext Component in the parent file.
  • The context states can be changed within child components of a page and not within pages. I do see folks trying to do this, especially when dealing with authentication. All states return back to their original state once you have moved to a new page or you refresh that same page. Cookies and local storage can help you retain its former state.

The files are now like this - src/context/successContext.js

import {createContext, useState} from 'react'
export const SuccessContext = createContext()

export default function SuccessContextComponent(
    {children}){ 
    const [showModal, setShowModal]= useState(false)                                   

    const setShowModalTrue=()=>{
        setShowModal(true)
    }
    const setShowModalFalse=()=>{
        setShowModal(false)
    }
    return(
        <SuccessContext.Provider value={{
            showModal, setShowModalFalse, setShowModalTrue
        }}>
            {children}
        </SuccessContext.Provider>
    )
}

src/pages/index.js

import { useEffect, useContext, useState} from "react";
import SuccessContextComponent, { SuccessContext } from "../context/successContext";

export default function Home() {  
  return (
    <SuccessContextComponent>
    <main>
      <SignupForm/>
      <SuccessModal/>
    </main>
    </SuccessContextComponent>
  );
}


function SignupForm() {  
  return (
  <section>
    <h4>Reg form</h4>
    <form>
      {['username', 'password', 'email'].map((name,ind)=>
        <input type="text" key={ind} placeholder={name}/>
      )}
    </form>
    <Button text="Signup"/>
  </section>
  );
}
function Button({text}) {
  const {setShowModalTrue}=useContext(SuccessContext)
  return (
    <button onClick={setShowModalTrue}>{text}</button>
  )
}
function SuccessModal() {     
  const {showModal, setShowModalFalse}=useContext(SuccessContext)

  if (!showModal) return null;
    return (
    <div
      className='navcont'
      style={{ padding: "0 30%", paddingTop: "10%" }}
    >
        <div className='mainnav'>
          <div
            style={{
              display: "flex",
              flexDirection: "column",
              alignItems: "center",
            }}
          >
          <svg style={{ alignSelf: "flex-end" }}
              className='close'
              onClick={setShowModalFalse} width="24" height="28" viewBox="0 0 24 28" fill="none" xmlns="http://www.w3.org/2000/svg">
            <g id="Close">
              <path id="Oval" fillRule="evenodd" clipRule="evenodd" d="M12 27.6217C18.6274 27.6217 24 21.4747 24 13.8919C24 6.30916 18.6274 0.162109 12 0.162109C5.37258 0.162109 0 6.30916 0 13.8919C0 21.4747 5.37258 27.6217 12 27.6217Z" fill="#F5F8FA"/>
              <g id="ic/close">
                <path id="icon/navigation/close" opacity="0.5" fillRule="evenodd" clipRule="evenodd" d="M15.1501 10.2937C15.0567 10.1865 14.9299 10.1263 14.7976 10.1263C14.6653 10.1263 14.5385 10.1865 14.4451 10.2937L12.0001 13.0854L9.55511 10.2879C9.46169 10.1808 9.33487 10.1206 9.20261 10.1206C9.07035 10.1206 8.94352 10.1808 8.85011 10.2879C8.65511 10.511 8.65511 10.8714 8.85011 11.0946L11.2951 13.892L8.85011 16.6895C8.65511 16.9126 8.65511 17.273 8.85011 17.4961C9.04511 17.7192 9.36011 17.7192 9.55511 17.4961L12.0001 14.6986L14.4451 17.4961C14.6401 17.7192 14.9551 17.7192 15.1501 17.4961C15.3451 17.273 15.3451 16.9126 15.1501 16.6895L12.7051 13.892L15.1501 11.0946C15.3401 10.8772 15.3401 10.511 15.1501 10.2937Z" fill="#AEB0C1"/>
              </g>
            </g>
          </svg>
            <h6 className='suc'>Great</h6>
            <p className='success'>
              You have registered successfully
            </p>
          </div>
        </div>
    </div>
  );

}

The link to the source code is [here]github.com/victorgbonna/hashnode/tree/day2-..

Thought Process

"UseContext" is a great alternative for controlling prop drilling. Use cases where using "useContext" is preferred include switching between a light and dark theme, multi-step forms with multiple components each having a separate group of inputs, and more.

My time has come; Thanks for having me.

If you have any questions, ask me in the comments section and I'll try to answer as soon as possible.