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

alexandprivate

Alex Suarez

Posted on March 1, 2020

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

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>
Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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 };
};

Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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

For Rhys Nicholls "closing the modal from within"
You can pass a component to handleModal function instead of a string then in that component you can destructure the handleModal function from the context and call that function on demand just like this...
component

function ContentComponent() {
  let { handleModal } = React.useContext(ModalContext);
  return (
    <>
      <p>Hello here !!!</p>
      <button
        className="h-8 px-3 text-white bg-red-500 text-xs rounded"
        onClick={handleModal}
      >
        Close modal
      </button>
    </>
  );
}

Enter fullscreen mode Exit fullscreen mode

and then import this component and use it in the handleModal

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(<ContentComponent />)}
      >
        open this modal!
      </button>
    </>
  );
};

Enter fullscreen mode Exit fullscreen mode

You may see the live example here https://codesandbox.io/s/eloquent-hamilton-vgbyq?file=/src/component.js:75-508https://codesandbox.io/s/eloquent-hamilton-vgbyq?file=/src/component.js:75-508

For Joel Robles Bentham "Open the modal on page load"
Simple call it on component mount, use and effect for it like

// the string could be a component as as well
 React.useEffect(() => {
    handleModal("This is component 2 modal content on page load");
  }, []);
Enter fullscreen mode Exit fullscreen mode

live example here https://codesandbox.io/s/eloquent-hamilton-vgbyq?file=/src/component2.js:160-261

💖 💪 🙅 🚩
alexandprivate
Alex Suarez

Posted on March 1, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related