Published: January 23 2018

React - Custom Modal Window / Dialog Box

Built with React 16.8.6 and Webpack 4.30.0

Other versions available:

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.

There are plenty of 3rd party libraries and plugins out there that include modal windows, and up until recently I used them myself when I needed to add a modal to a project. The main issue that I have with 3rd party libraries is that they usually contain a lot of features I don't need which adds unnecessary bloat to my application, so a while back I took the time to create a custom modal window in Angular 1 to see how difficult it would be and also to remove the mystery I had in my mind about exactly how modals work.

Once I'd finished I was surprised at the relatively small amount of code required to implement a custom modal window, most of the modal 'magic' is done with a handful of css styles (see the jw-modal.less file) while JavaScript/JSX is just used for showing and hiding the modal windows.

The react modal component also available as an npm package, demo and details are available at npm - JW React Modal Dialog.

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)

Update History:
  • 16 Apr 2019 - Updated to React 16.8.6 and Webpack 4.30.0
  • 23 Jan 2018 - Built with React 16.2.0 and Webpack 3.10.0


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>

IMPORTANT: 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 Modal Dialog Project Structure

All source code for the React modal 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 };

 


Need Some React Help?

Search fiverr for freelance React developers.


Follow me for updates

On Twitter or RSS.


When I'm not coding...

Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!


Comments


Supported by