Alex Suarez
Posted on March 1, 2020
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={() => {}}
>
×
</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()}
>
×
</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
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>
</>
);
}
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>
</>
);
};
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");
}, []);
live example here https://codesandbox.io/s/eloquent-hamilton-vgbyq?file=/src/component2.js:160-261
Posted on March 1, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.