I'm uncomfortable with the extent that ReactJS commingles style, content, and javascript. But apart from that reservation, it's a lot of fun to build components with ReactJS. The modular aspect can be quite tantalizing. In this tutorial I'm going to show you how to implement a flawless modal in ReactJS.
I'll start by showing you the finished product. Next, we'll work backward to see how we got here.
Link to Github gist.
To focus on the ReactJS aspect of creating a modal, I decided to ignore the CSS/design. Therefore I used Materialize's CSS framework. Here is their documentation for models.
For testing you can include Materialize's CSS framework as follows:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.8/css/materialize.min.css">
The starting point is recognizing that we need to use state
to toggle the modal open or closed. We first set the modal to be closed in its initial state:
this.state = {
toggle: false
}
We next want to set this.state.toggle
to true when an event fires.
Normally we would set a new state like this:
toggle(event) {
this.setState({toggle: true});
}
But we can't just hardcode the new state to true because we also want to be able to close the modal (make this.state.toggle false again).
Calling the toggle class method this.toggle()
should negate the previous state (!prevState
).
toggle(event) {
this.setState(prevState => ({
toggle: !prevState.toggle
}));
}
For our working example the modal opens after an onClick
event, but this does not necessarily need to be the case. For example we could use a lifecycle event to set the React Modal to open after 5 seconds. One use case for this is an email newsletter optin modal.
We want to change this.state.toggle
from false to true with an onClick event.
Here's how we do it:
<a className="btn" onClick={this.toggle}>Modal</a>
this.toggle is a class method which is not bound by default. If you neglect to bind this.toggle
and pass it to onClick, this
will be undefined when the function is called.
The React authors recommend binding class methods in the constructor, which is why we write
this.toggle = this.toggle.bind(this)
in the constructor as follows:
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.state = {toggle: false}
}
Thus far we've discussed how an onClick event can negate this.state.toggle with the class method this.toggle().
There are many variations on how we can translate this binary toggle to actually displaying or hiding the modal. Another option would be wrapping the modal.push(...{some JSX}...) in a conditional.
In this example I apply a style to the outermost modal div
.
ReactJS supports inline styles which have the following pattern:
const display = {
display: 'block'
};
const hide = {
display: 'none'
};
We then use a ternary conditional operator to only display the modal if this.state.toggle is set to true. A ternary conditional operator is really just an inline, shorthand form of an if-else statement. In vanilla javascript it would be:
var foo = true;
if(foo) {
console.log('foo!');
} else {
console.log('bar!');
}
A ternary operator conditional looks like:
foo ? console.log('foo!') : console.log('bar!');
But in React, if we are using JSX we need to enclose the javascript in {..} when it appears inside the return()
function
In our example we use a ternary operator to check if this.state.toggle
is true. If it returns true we apply the css declared in the constant display
that appears in the first few lines of code. Otherwise hide
applies the inline CSS display: none
.
<div className="modal" style={this.state.toggle ? display : hide}>
const display = {
display: 'block'
};
const hide = {
display: 'none'
};
class Modal extends React.Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.state = {
toggle: false
}
}
toggle(event) {
this.setState(prevState => ({
toggle: !prevState.toggle
}));
}
render() {
var modal = [];
modal.push(
<div className="modal" style={this.state.toggle ? display : hide}>
<div className="modal-content">
<h4>Modal Header</h4>
<p>A bunch of text</p>
</div>
<div className="modal-footer">
<a className="btn" onClick={this.toggle}>Agree</a>
</div>
</div>
);
return (
<div>
<a className="btn" onClick={this.toggle}>Modal</a>
{modal}
</div>
);
}
}
ReactDOM.render(<Modal />, document.getElementById("root"));