I'd like to show how quick and easy it is to create a modal window component with ReactJS.
I needed a component for modal windows for a project and before reaching for Google I thought I'd try and see how hard it would be to create one from scratch.
Let's call our modal window component Modal
and here is how we want to use it
in our application.
class App extends React.Component {
constructor(props) {
super(props)
this.state = { isModalOpen: false }
}
render() {
return (
<div>
<button onClick={() => this.openModal()}>Open modal</button>
<Modal isOpen={this.state.isModalOpen} onClose={() => this.closeModal()}>
<h1>Modal title</h1>
<p>hello</p>
<p><button onClick={() => this.closeModal()}>Close</button></p>
</Modal>
</div>
)
}
openModal() {
this.setState({ isModalOpen: true })
}
closeModal() {
this.setState({ isModalOpen: false })
}
}
So when isModalOpen
in our application component state is set to true
,
the modal will be displayed, otherwise it will be hidden.
The way to open or close modal then is to simply set the value of isModalOpen
.
Our modal component will be very simple. It will consist of content that will be absolutely positioned in the middle of the screen on top of all other elements on the page. And we will also have a grey background overlaying all other elements on the page.
Here is how to create the grey background overlay also known as backdrop.
<style>
.backdrop {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
z-index: 9998;
background: rgba(0, 0, 0, 0.3);
}
</style>
<div class="backdrop"></div>
We make this div
span the whole screen (width: 100%
and height: 100%
)
and we make its position
to be absolute
which means we can position it everywhere on the page.
In this case, we just put it in the top left corner (top: 0px
and left: 0px
)
but since it has 100%
width and height it will cover the whole screen.
To make sure that it is really on top of all other elements, we set z-index
to a high value
(by default all elements have a z-index
value of 0
).
Finally, we get the transparent grey effect by setting the background color to be
black but only at 30% opacity. #000
or #000000
or rgb(0,0,0)
represent the black color
and we can make it 30% transparent by setting the type to rgba
(notice the a
)
and 30%
is represented as 0.3
.
Now, we can do almost the same thing with the modal window!
<style>
.modal {
width: 300px;
height: 300px;
position: absolute;
top: 50%;
left: 50%;
margin-top: -150px;
margin-left: -150px;
z-index: 9999;
background: yellow;
}
</style>
<div class="modal"></div>
In this case, our modal window size is going to be 300x300
.
We again position it absolutely but this time set the top left corner of our modal window to be in the middle
of the screen by using (top: 50%
and left: 50%
).
To center it properly, we can move it to the left a little bit with margin-left
of half its size -150px
, same with margin-top
.
Then we just make sure it's on top of everything else (z-index: 9999
) including the backdrop (z-index: 9998
).
The yellow background is there to help us see the window.
Now, what if we don't want to set a fixed size for our modal window?
There is some CSS3 magic that we can do.
.modal {
position: absolute,
top: 50%,
left: 50%,
transform: translate(-50%, -50%),
z-index: 9999,
background: yellow;
}
This time, instead of specifying width
and height
and correcting the position with negative margin-left
and margin-top
,
we are using tranform: translate(x, y)
which understands percentages and will calculate the necessary size for us.
The translate
transformation moves an element from its current position by x
pixels to the right and y
pixels down.
Knowing all that, our basic modal component will look like this.
class Modal extends React.Component {
render() {
if (this.props.isOpen === false)
return null
let modalStyle = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
zIndex: '9999',
background: '#fff'
}
let backdropStyle = {
position: 'absolute',
width: '100%',
height: '100%',
top: '0px',
left: '0px',
zIndex: '9998',
background: 'rgba(0, 0, 0, 0.3)'
}
return (
<div>
<div style={modalStyle}>{this.props.children}</div>
<div style={backdropStyle} onClick={e => this.close(e)}/>}
</div>
)
}
close(e) {
e.preventDefault()
if (this.props.onClose) {
this.props.onClose()
}
}
}
If isOpen
property is true
or not set, then we will render the contents of the modal window.
Otherwise, if isOpen
is explicitly set to false
, we render nothing and the modal is not visible.
Next, we transfer our style to a JavaScript object literal. Notice that z-index
becomes zIndex
.
Finally, we render our div
s with the appropriate styles and when the user clicks on the backdrop
we raise the onClose
event that our application state component can listen to.
We can make our modal component more fancy by letting the user optionally specify width and height
if (this.props.width && this.props.height) {
modalStyle.width = this.props.width + 'px'
modalStyle.height = this.props.height + 'px'
modalStyle.marginLeft = '-' + (this.props.width/2) + 'px',
modalStyle.marginTop = '-' + (this.props.height/2) + 'px',
modalStyle.transform = null
}
or override modal style
if (this.props.style) {
for (let key in this.props.style) {
modalStyle[key] = this.props.style[key]
}
}
and/or provide custom class names for additional styling using separate CSS
return (
<div className={this.props.containerClassName}>
<div className={this.props.className} style={modalStyle}>
{this.props.children}
</div>
{!this.props.noBackdrop &&
<div className={this.props.backdropClassName} style={backdropStyle}
onClick={e => this.close(e)}/>}
</div>
)
Final result
Here is the complete example.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React Modal Demo</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/JSXTransformer.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/jsx">
class App extends React.Component {
constructor(props) {
super(props)
this.state = { isModalOpen: false }
}
render() {
return (
<div>
<button onClick={() => this.openModal()}>Open modal</button>
<Modal isOpen={this.state.isModalOpen} onClose={() => this.closeModal()}>
<h1>Modal title</h1>
<p>hello</p>
<p><button onClick={() => this.closeModal()}>Close</button></p>
</Modal>
</div>
)
}
openModal() {
this.setState({ isModalOpen: true })
}
closeModal() {
this.setState({ isModalOpen: false })
}
}
class Modal extends React.Component {
render() {
if (this.props.isOpen === false)
return null
let modalStyle = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
zIndex: '9999',
background: '#fff'
}
if (this.props.width && this.props.height) {
modalStyle.width = this.props.width + 'px'
modalStyle.height = this.props.height + 'px'
modalStyle.marginLeft = '-' + (this.props.width/2) + 'px',
modalStyle.marginTop = '-' + (this.props.height/2) + 'px',
modalStyle.transform = null
}
if (this.props.style) {
for (let key in this.props.style) {
modalStyle[key] = this.props.style[key]
}
}
let backdropStyle = {
position: 'absolute',
width: '100%',
height: '100%',
top: '0px',
left: '0px',
zIndex: '9998',
background: 'rgba(0, 0, 0, 0.3)'
}
if (this.props.backdropStyle) {
for (let key in this.props.backdropStyle) {
backdropStyle[key] = this.props.backdropStyle[key]
}
}
return (
<div className={this.props.containerClassName}>
<div className={this.props.className} style={modalStyle}>
{this.props.children}
</div>
{!this.props.noBackdrop &&
<div className={this.props.backdropClassName} style={backdropStyle}
onClick={e => this.close(e)}/>}
</div>
)
}
close(e) {
e.preventDefault()
if (this.props.onClose) {
this.props.onClose()
}
}
}
ReactDOM.render(<App/>, document.getElementById('app'))
</script>
</body>
</html>