January 23 2018

React - Custom Modal Window / Dialog Box

Built with React 16.2.0

UPDATE 23 Feb 2018 - The modal component is now also available on npm, for details check out npm - JW React Modal Dialog.

In this tutorial we'll cover how to implement modal windows (dialog boxes) in React. The example is a custom modal without the need for any 3rd party libraries.

For the same example in Angular 2 check out this post.

Tutorial Code on GitHub

The tutorial code is available on GitHub at https://github.com/cornflourblue/react-custom-modal.

Here it is in action: (See on StackBlitz at https://stackblitz.com/edit/react-custom-modal-dialog)


Running the React Modal Example Locally

  1. Install NodeJS and NPM from https://nodejs.org/en/download/.
  2. Download the project source code from https://github.com/cornflourblue/react-custom-modal
  3. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  4. Start the application by running npm start from the command line in the project root folder.


React Custom Modal Setup

There are two main pieces of code that make up this custom modal implementation:

  • React Custom Modal Component in src/_components/JwModal.jsx
  • Modal LESS/CSS styles in src/_content/jw-modal.less


Adding a React Modal to your page

Once you have the above two files (or the code from them) integrated into your project, you can add modals to your pages with the custom <modal> tag like this:

<JwModal id="my-custom-modal">
    <h1>A Custom Modal!</h1>
    <button onClick={JwModal.close('my-custom-modal')}>Close</button>
</JwModal>

A unique id is required for each modal on a page, this is passed to the modal component to identify which modal you want to open/close.

You can add any html you like inside the JwModal tag, you can also update the modal LESS/CSS if you want to change the styles of the modals, e.g to make them smaller or add CSS transitions etc.


Opening & Closing React Modals

To open and close (show/hide) a modal dialog you need to use the JwModal component by importing it into your React component and calling JwModal.open('[modal id]') and JwModal.open('[modal id'), e.g:

import React from 'react';

import { JwModal } from '../_components';

class HomePage extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            bodyText: 'This text can be updated in modal 1'
        };

        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(e) {
        const { name, value } = e.target;
        this.setState({ [name]: value });
    }

    render() {
        const { bodyText } = this.state;
        return (
            <div>
                <h1>Home</h1>
                <p>{bodyText}</p>
                <button onClick={JwModal.open('custom-modal-1')}>Open Modal 1</button>
                <button onClick={JwModal.open('custom-modal-2')}>Open Modal 2</button>

                <JwModal id="custom-modal-1">
                    <h1>A Custom Modal!</h1>
                    <p>
                        Home page text: <input type="text" name="bodyText" value={bodyText} onChange={this.handleChange} />
                    </p>
                    <button onClick={JwModal.close('custom-modal-1')}>Close</button>
                </JwModal>

                <JwModal id="custom-modal-2">
                    <h1 style={{height: 1000}}>A Tall Custom Modal!</h1>
                    <button onClick={JwModal.close('custom-modal-2')}>Close</button>
                </JwModal>
            </div>
        );
    }
}

export { HomePage };


React Project Structure

All source code for the React example app is located in the /src folder. Inside the src folder there is a folder per feature (App, HomePage, TestPage) and folders for non-feature code that can be shared across different parts of the app (_components, _content).

I prefixed non-feature folders with an underscore "_" to group them together and make it easy to distinguish between features and non-features, it also keeps the project folder structure shallow so it's quick to see everything at a glance from the top level and to navigate around the project.

The index.js files in each folder are barrel files that group all the exported modules together so they can be imported using the folder path instead of the full module path and to enable importing multiple modules in a single import (e.g. import { componentOne, componentTwo } from '../_components').

 

Breakdown of the React Custom Modal Code

Below is a breakdown of the pieces of code used to implement the custom modal example in React, you don't need to know the details of how it all works to use the modals in your project, it's only if you're interested in the nuts and bolts or if you want to modify the code or behaviour.


LESS/CSS Styles for the React Modal

These are the styles applied to the custom modals in this example, however they could also be used in non-react projects as it's just pure LESS/CSS.

/* MODAL STYLES
-------------------------------*/
.jw-modal {
    /* modal container fixed across whole screen */
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;

    /* z-index must be higher than .modal-background */
    z-index: 1000;
    
    /* enables scrolling for tall modals */
    overflow: auto;

    .jw-modal-body {
        padding: 20px;
        background: #fff;

        /* margin exposes part of the modal background */
        margin: 40px;
    }
}

.jw-modal-background {
    /* modal background fixed across whole screen */
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;

    /* semi-transparent black  */
    background-color: #000;
    opacity: 0.75;
    
    /* z-index must be below .modal and above everything else  */
    z-index: 900;
}

body.jw-modal-open {
    /* body overflow is hidden to hide main scrollbar when modal window is open */
    overflow: hidden;
}


React Modal Component

The modal component manages the communication that's required between app components and modal component instances. It maintains a current list of available modals in a static array and exposes static methods for interacting with those modals.

import React from 'react';
import PropTypes from 'prop-types';

const propTypes = {
    id: PropTypes.string.isRequired
};

class JwModal extends React.Component {
    static modals = [];

    static open = (id) => (e) => {
        e.preventDefault();

        // open modal specified by id
        let modal = JwModal.modals.find(x => x.props.id === id);
        modal.setState({ isOpen: true });
        document.body.classList.add('jw-modal-open');
    }

    static close = (id) => (e) => {
        e.preventDefault();

        // close modal specified by id
        let modal = JwModal.modals.find(x => x.props.id === id);
        modal.setState({ isOpen: false });
        document.body.classList.remove('jw-modal-open');
    }

    constructor(props) {
        super(props);

        this.state = { isOpen: false };

        this.handleClick = this.handleClick.bind(this);
    }

    componentDidMount() {
        // move element to bottom of page (just before </body>) so it can be displayed above everything else
        document.body.appendChild(this.element);

        // add this modal instance to the modal service so it's accessible from other components
        JwModal.modals.push(this);
    }

    componentWillUnmount() {
        // remove this modal instance from modal service
        JwModal.modals = JwModal.modals.filter(x => x.props.id !== this.props.id);
        this.element.remove();
    }

    handleClick(e) {
        // close modal on background click
        if (e.target.className === 'jw-modal') {
            JwModal.close(this.props.id)(e);
        }
    }
    
    render() {
        return (
            <div style={{display: + this.state.isOpen ? '' : 'none'}} onClick={this.handleClick} ref={el => this.element = el}>
                <div className="jw-modal">
                    <div className="jw-modal-body">
                        {this.props.children}
                    </div>
                </div>
                <div className="jw-modal-background"></div>
            </div>
        );
    }
}

JwModal.propTypes = propTypes;

export { JwModal };

 

Web Developer Sydney

Feel free to drop me a line if you're looking for web developer or React developer in Sydney Australia, I also provide remote contracting services for clients outside Sydney.


Sponsored by