Search code examples
reactjsreact-dom

Close React Modal


I'm having a lot of problems trying to figure out how to add a functioning close button to my modal - constructor/props hasn't been working and I'm not sure what to put after onClick= in the button element.

class Modal extends React.Component {

// whenever component gets rendered to the screen we create a new div assigned to this.modalTarget
	componentDidMount() {
		this.modalTarget = document.createElement('div');
		// add class name to modal target
		this.modalTarget.className = 'modal';
		// take the div we just created and append it to the body tag in doc
		document.body.appendChild(this.modalTarget);
		// call method _render
		this._render();
	}

// whenever the component's about to update we do another render
// this render makes sure that if we get a new set of components or children in the modal
// we're going to render those to the parent div as well
	componentWillUpdate() {
		this._render();
	}

// clean up - whenever the component is about to unmount from the screen
// cleans up dom when the modal is removed from the component heirarchy
	componentWillUnmount() {
		// unmounts this.props.children
		ReactDOM.unmountComponentAtNode(this.modalTarget);
		document.body.removeChild(this.modalTarget);
	}

	_render() {
		// take react dom library and render a div that contains this.props.children
		// and render it into this.modalTarget
		ReactDOM.render(
			<Provider store= {store}>
				<Router>
					<div className="modal">
						{this.props.children}
						<button>Close</button>
					</div>
				</Router>
			</Provider>,
			this.modalTarget


Solution

  • Several issues here. First, move away from directly manipulating the DOM. React uses a virtual DOM, so you don't need to manually add or remove DOM elements. React automatically handles this DOM manipulation through the render method. Also, you'll need to control this Modal utilizing some sort of state (isOpen). It be can through React's local state or through Redux's state. Either way, it needs to be controlled and compared against. Put simply, if it's open, render the Modal, if it's closed, render null.

    In addition, this Modal component can be structured to be reusable. Simply add it as a child to another stateful parent component and include whatever children you want to render inside of it.

    Working example:

    Edit Simple Modal


    components/Example.js (parent component)

    import React, { Component } from "react";
    import Modal from "../Modal";
    import "./styles.css";
    
    class Example extends Component {
      state = { isOpen: false };
    
      handleOpenModal = () => {
        this.setState({ isOpen: true });
      };
    
      handleCloseModal = () => {
        this.setState({ isOpen: false });
      };
    
      render = () => (
        <div className="example">
          <h2>Simple Modal Example</h2>
          <button
            className="uk-button uk-button-primary uk-button-small"
            onClick={this.handleOpenModal}
          >
            Open Modal
          </button>
          <Modal isOpen={this.state.isOpen} onCloseModal={this.handleCloseModal}>
            <h1 className="title">Hello!</h1>
            <p className="subtitle">There are two ways to close this modal</p>
            <ul>
              <li>Click outside of this modal in the grey overlay area.</li>
              <li>Click the close button below.</li>
            </ul>
            <button
              className="uk-button uk-button-danger uk-button-small"
              onClick={this.handleCloseModal}
            >
              Close
            </button>
          </Modal>
        </div>
      );
    }
    
    export default Example;
    

    components/Modal.js (child component -- this has a lot of smaller components that were separated for reusability and ease of understanding -- they're basically simple divs with some styles attached -- see notes below)

    import React from "react";
    import PropTypes from "prop-types";
    import BackgroundOverlay from "../BackgroundOverlay"; // grey background
    import ClickHandler from "../ClickHandler"; // handles clicks outside of the modal
    import Container from "../Container"; // contains the modal and background
    import Content from "../Content"; // renders the "children" placed inside of <Modal>...</Modal>
    import ModalContainer from "../ModalContainer"; // places the modal in the center of the page
    
    // this is a ternary operator (shorthand for "if/else" -- if cond ? then : else)
    // below can be read like: if isOpen is true, then return/render the modal, else return null
    const Modal = ({ children, isOpen, onCloseModal }) =>
      isOpen ? (
        <Container>
          <BackgroundOverlay />
          <ModalContainer>
            <ClickHandler isOpen={isOpen} closeModal={onCloseModal}>
              <Content>{children}</Content>
            </ClickHandler>
          </ModalContainer>
        </Container>
      ) : null;
    
    // these proptype declarations are to ensure that passed down props are 
    // consistent and are defined as expected
    Modal.propTypes = {
      children: PropTypes.node.isRequired, // children must be a React node
      isOpen: PropTypes.bool.isRequired, // isOpen must be a boolean
      onCloseModal: PropTypes.func.isRequired // onCloseModal must be a function
    };
    
    export default Modal;