ui > Modal
React Modal Tutorial with Portals
In this tutorial we're going to build a Modal popup component rendered through the use of React portals. A portal allows you to render a component outside of the current parent/child hierarchy. Because you always want your Modal to appear "on top" of all your other React components, a portal is the perfect use case to "teleport" rendering there.
In addition to building and rendering the Modal through the portal, we're also going to show a very common use-case of setting and retrieving data from this Modal. Try out the below example to observe this behavior.
// Somewhere at the top of your app<div id="app-modal"/>
Full Example Code
import React, { useState } from 'react';import ReactDOM from 'react-dom';import styled from 'styled-components';const Modal = styled.div`max-width: 500px;background-color: white;position: fixed;top: 75px;z-index: 5;max-height: calc(100% - 200px);left: calc(50% - 250px);display: flex;flex-direction: column;@media (max-width: 500px) {left: 0px;margin: 0px 10px;}`;export const ModalContent = styled.div`overflow: auto;min-height: 200px;padding: 0px 40px;padding-bottom: 80px;`;export const ModalFooter = styled.div`box-shadow: 0px -2px 10px 0px grey;height: 60px;display: flex;justify-content: center;`;export const ConfirmButton = styled.div`margin: 10px;color: white;height: 40px;border-radius: 5px;padding: 5px;text-align: center;width: 200px;cursor: pointer;background-color: blue;`;const ModalShadow = styled.div`position: fixed;height: 100%;width: 100%;top: 0px;background-color: black;opacity: 0.7;z-index: 4;`;const ModalBanner = styled.div`margin-bottom: 20px;background-color: blue;color: white;padding: 10px;`;const Input = styled.input`text-align: right;width: 200px;margin-left: 15px;`;export const MainButton = styled.button``;function ModalContainer({ setOpen, data, setData }) {const [localData, setLocalData] = useState(data);const { clicks } = localData;function close() {setOpen(false);}function submit() {setData({clicks,});close();}const content = new Array(1).fill(<p>Edit the clicks below by clicking on the number input or typing in yourown value.</p>,);return ReactDOM.createPortal(<><ModalShadow onClick={close} /><Modal><ModalBanner>Edit Clicks</ModalBanner><ModalContent>{content}<label>Clicks<Inputvalue={clicks}type="number"onChange={e => setLocalData({ clicks: e.target.value })}/></label></ModalContent><ModalFooter><ConfirmButton onClick={submit}> Submit </ConfirmButton></ModalFooter></Modal></>,document.getElementById('app-modal'),);}export function ModalExample(props) {const [open, setOpen] = useState(false);const [data, setData] = useState({ clicks: 0 });return (<div><div>Clicks: {data.clicks}</div><MainButtononClick={() => {setOpen(true);}}>OPEN MODAL</MainButton>{open && (<ModalContainer{...props}setOpen={setOpen}data={data}setData={setData}/>)}</div>);}
Using Hooks with React Modals
You can see that we kick off the example with some useState
hooks.
const [open, setOpen] = useState(false);const [data, setData] = useState({ clicks: 0 });
Our goal is to track the state of the modal (open or closed) and then the click data (initialized to an object with a clicks
attribute).
Typically you will want to store other data in this object because you're tracking more than one attribute, so that's why we used an object
instead of a single number.
We are then logging the clicks with: <div>Clicks: {data.clicks}</div>
.
Next we create a MainButton
styled-component (we used our own style in this example), to open the Modal.
We use our setOpen
setter to set the open
state to true
(it was default false
).
This is now the boolean signal to mount the ModalContainer
. This doesn't get mounted because expressions of the form expression && (something)
do not evaluate
the (something)
unless the expression
is true. So that's how we conditionally render the Modal with React hooks.
React Modal Main CSS Walkthrough
The main Modal element is a box that sits in the center of the browser. The position: fixed
CSS sets the Modal
to be rendered relative to the browser's viewport. We then use top
& left
to center the modal. We ensure that the
modal never grows larger than the browser's height by setting max-height: calc(100% - 100px)
. We set the Modal to be
a flexbox so that we can allow scrollable content in the center with the overflow: auto
attribute (see ModalContent
below). If the element was
not a flexbox, the ModalFooter
would not stick to the bottom of the Modal when the height of the Modal is decreased.
const Modal = styled.div`// Need to specify a max widthmax-width: 500px;background-color: white;// What allows us to position relative to the viewportposition: fixed;// Some padding from the top of the browsertop: 75px;// Arbitrary value so it covers other z-index componentsz-index: 5;// Always maintains height of browser minus 200pxmax-height: calc(100% - 200px);// Always stays centered (250px is half of 500px)left: calc(50% - 250px);// It's a column flexbox so that we can ensure the footer is locked at the bottomdisplay: flex;flex-direction: column;// On mobile, we don't need to center so set left to 0px// Also add a slight margin on the left and right@media (max-width: 500px) {left: 0px;margin: 0px 10px;}`;
The Modal Shadow
This is another fixed element that stretches the entire browser's viewport. By setting a black background and an opacity of 70%, we create a translucent shadow on the rest of our content so that the Modal becomes clearly visible.
const ModalShadow = styled.div`position: fixed;height: 100%;width: 100%;top: 0px;background-color: black;opacity: 0.7;z-index: 4;`;
Scrolling Modal Content and Locked Modal Footer
The below example demonstrates the scrolling content in the center of the Modal by adding a bunch of extra text.
The overflow: auto
on the ModalContent
causes a scrollable container to be created, while letting the ModalFooter
to be
kept in view as to appear stuck to the bottom.
export const ModalContent = styled.div`// allows scrolling inside the contentoverflow: auto;min-height: 200px;padding: 0px 40px;padding-bottom: 80px;`;
We set the ModalFooter
to also be a flexbox so that we can center the button inside.
export const ModalFooter = styled.div`// shadow above the footer elementbox-shadow: 0px -2px 10px 0px grey;height: 60px;// Make a flexbox to put the confirm button in the centerdisplay: flex;justify-content: center;`;
🛈 React School creates templates and video courses for building beautiful apps with React. Download our free Material UI template.