Your next React Modal with your own "useModal" Hook & Context API.
Tags: react, context, hooks, portals
Published: 2020-03-01 01:51:29


*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.

```html
<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.

```javascript
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.

```javascript
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...

```javascript
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.

```javascript
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__

```javascript
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

```javascript
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](https://codesandbox.io/s/eloquent-hamilton-vgbyq)

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](https://www.pexels.com/@rodolphozanardo)*


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

```javascript 
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>
    </>
  );
}

```
and then import this component and use it in the handleModal

```javascript
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>
    </>
  );
};

```

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 

```javascript
// the string could be a component as as well
 React.useEffect(() => {
    handleModal("This is component 2 modal content on page load");
  }, []);
```
live example here https://codesandbox.io/s/eloquent-hamilton-vgbyq?file=/src/component2.js:160-261