loading...
Cover image for Your next React Modal with your own "useModal" Hook & Context API.

Your next React Modal with your own "useModal" Hook & Context API.

alexandprivate profile image Alex Suarez Updated on ・4 min read

Hi there everyone, this is a quick review about how to use Modals Components in your React project combining Hooks, Context, and Portals. You need to have some experience coding with React, and be aware of React's latest updates like Hooks and Context API. Let's do it.

The Modal Component

Before writing our Modal Component, let's open our public/index.html (or the HTML where you render your JS code) and add a new tag to render out the Modal Component using a React Portal.

<body>
  <noscript>
    You need to enable JavaScript to run this app.
  </noscript>
  <div id="modal-root"></div>
  <div id="root"></div>
</body>

Now let's write our Modal Component, and use the createPortal function, createPortal function expects two parameters, the first is the actual JSX and the second the DOM element where it will be rendered it.

import React from "react";
import ReactDOM from "react-dom";

const Modal = () => {
  return ReactDOM.createPortal(
    <div
      className="fixed top-0 left-0 h-screen w-full flex items-center justify-center"
      style={{ background: "rgba(0,0,0,0.8)" }}
    >
      <div className="bg-white relative p-5 shadow-lg rounded flex flex-col items-start text-lg text-gray-800">
        <button
          className="absolute top-0 right-0 -mt-12 font-bold self-end rounded-full bg-red-200 mb-3 bg-white text-red-700 w-8 h-8"
          onClick={() => {}}
        >
          &times;
        </button>
        <p>I am the Modal</p>
      </div>
    </div>,
    document.querySelector("#modal-root")
  );
};

export default Modal;

useModal Hook

This custom Hook is going to hold our Modal Component states, but first let's remind what a Hook is according react docs:

React Hooks are functions that let us hook into the React state and lifecycle features from function components. By this, we mean that hooks allow us to easily manipulate the state of our functional component without needing to convert them into class components

In other words, Hooks allow us to create "shareable models" of states and methods to manipulate those states, by returning both we can reuse it across components, and we can avoid code duplication in our project. If we have more than one component that initializes the same state structure and methods it may be a good idea to extract those in a custom hook, and we can have the state and the methods in one place and reuse it. This is our custom useModal React Hook.

import React from "react";

export default () => {
  let [modal, setModal] = React.useState(false);
  let [modalContent, setModalContent] = React.useState("I'm the Modal Content");

  let handleModal = (content = false) => {
    setModal(!modal);
    if (content) {
      setModalContent(content);
    }
  };

  return { modal, handleModal, modalContent };
};

Every Hook we create, as a rule, needs to start with the word "use".
Now you may think you can share actual states values across components with Hooks ... Sadly the answer is NO, every time you use a Hook in a component and you extract the state from the Hooks, this creates a "local state" only visible within that component, if you want to pass that actual state to a children component this has to be done via props or in this case using React Context

React Context

We are going to use our newly created React Hook in our ModalContext...

import React from "react";
import useModal from "./useModal";
import Modal from "./modal";

let ModalContext;
let { Provider } = (ModalContext = React.createContext());

let ModalProvider = ({ children }) => {
  let { modal, handleModal, modalContent } = useModal();
  return (
    <Provider value={{ modal, handleModal, modalContent }}>
      <Modal />
      {children}
    </Provider>
  );
};

export { ModalContext, ModalProvider };

Now let's do a simple modification in our modal component to start using our context info there as props.

import React from "react";
import ReactDOM from "react-dom";
import { ModalContext } from "./modalContext";

const Modal = () => {
  let { modalContent, handleModal, modal } = React.useContext(ModalContext);
  if (modal) {
    return ReactDOM.createPortal(
      <div
        className="fixed top-0 left-0 h-screen w-full flex items-center justify-center"
        style={{ background: "rgba(0,0,0,0.8)" }}
      >
        <div className="bg-white relative p-5 shadow-lg rounded flex flex-col items-start text-lg text-gray-800">
          <button
            className="absolute top-0 right-0 -mt-12 font-bold self-end rounded-full bg-red-200 mb-3 bg-white text-red-700 w-8 h-8"
            onClick={() => handleModal()}
          >
            &times;
          </button>
          <p>{modalContent}</p>
        </div>
      </div>,
      document.querySelector("#modal-root")
    );
  } else return null;
};

export default Modal;

Now let's move to the app.js component and let's start using our Modal Component and the Context Provider

import React from "react";
import { ModalProvider } from "./modalContext";
import Component from "./component";
import Component2 from "./component2";

export default function App() {
  return (
    <div className="App container mx-auto px-8 text-gray-700">
      <h1 className="text-3xl">Hello CodeSandbox</h1>
      <h2 className="text-xl mb-6">Start editing to see some magic happen!</h2>
      <ModalProvider>
        <Component />
        <Component2 />
      </ModalProvider>
    </div>
  );
}

You will notice a couple of components there "Component and Component2" those are some dummy components that hold a button to open the Modal, the main difference between them is the message to render inside our Modal

import React from "react";
import { ModalContext } from "./modalContext";

const Component = () => {
  let { handleModal } = React.useContext(ModalContext);

  return (
    <>
      <p>
        Lorem ipsum dolor sit amet consectetur, adipisicing elit. Cumque quidem
        asperiores?
      </p>
      <button
        className="mt-6 rounded  bg-purple-700 text-purple-100 px-5 h-12"
        onClick={() => handleModal("This is component modal content")}
      >
        open this modal!
      </button>
    </>
  );
};

export default Component;

You will end it up with something like this CodeSandbox Modal Demo

And that's it, I tried to make this as short as possible without digging into the code's specific parts, please if you have any doubts about the code or a different approach let me know at the comments.

Picture by Rodolpho Zanardo, Pexels

Posted on by:

alexandprivate profile

Alex Suarez

@alexandprivate

Former Physician, Sr. Frontend Engineer / UI Designer, coding for the last 10 years. Engineer II at @everymundo

Discussion

markdown guide
 

Hi Alex. Excellent tutorial which has helped me alot and I have a follow up question
I'm wanting to close the modal using a button nested inside of modalContent passed to handleModal.
How would I go about this?
I have tried using handleModal for the onClick of the button but nothing happens.
This may be a hole in my knowledge

 

Hi, excellent job. But i get stoked when i wanted to open a modal after some other action besides the clicking, for example if i want to open a pop up modal on page ready.
it can be done?