How To Create A Modal In ReactJS [Tutorial]

March 14, 2018

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.

ReactJS Modal Demo

I'll start by showing you the finished product. Next, we'll work backward to see how we got here.

Link to Github gist.

Modal Styling

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">

Step One: State and onClick Events

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.

Creating A React onClick Event

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}
}

Displaying The Model

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}>

Summary

  • Don't forget to bind the method class <code>this.toggle()</code> in the constructor
  • Use <code>this.state</code> as the "source of truth" about whether the modal should be displayed or hidden
  • Negate the prior state when the eventHandler is called with <code>!prevState</code> to allow the modal to be closed again.

The Finished Code

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"));