• 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.

    <p data-height="265" data-theme-id="0" data-slug-hash="ENJjzL" data-default-tab="result" data-user="binarygourmet" data-embed-version="2" data-pen-title="React Modal Example" class="codepen"></p> <script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>

    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 <code>state</code> 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 <code>this.state.toggle</code> 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 <code>this.toggle()</code> should negate the previous state (<code>!prevState</code>).

    toggle(event) {
        this.setState(prevState => ({
          toggle: !prevState.toggle
        }));
    }

    For our working example the modal opens after an <code>onClick</code> 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 <code>this.state.toggle</code> 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 <code>this.toggle</code> and pass it to onClick, <code>this</code> 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 <code>div</code>.

    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 <code>return()</code> function

    In our example we use a ternary operator to check if <code>this.state.toggle</code> is true. If it returns true we apply the css declared in the constant <code>display</code> that appears in the first few lines of code. Otherwise <code>hide</code> applies the inline CSS <code>display: none</code>.

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