Skip to content
Advertisement

React Custom Modal Component Problems With Updating Same Component Used Repeatedly

I built a custom Modal.

There is one particular function I would like it to do when opened. I would like a CSS class to be toggled when this modal is opened/closed.

This works just fine if I only insert this component once in a template. But in my case I am inserting it three times. By using the componentDidMount I insert some JS that should toggle the CSS class. It does not do it for the first or the second modal, it will only do it for the third.

CODE UPDATED!

This is the parent component:

import React from "react";
import ModalSmall from "./ModalSmall";
import ModalMedium from "./ModalMedium";
import ModalLarge from "./ModalLarge";
import "bootstrap/dist/css/bootstrap.css";
import "./styles.scss";

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isModalSmallOpen: false,
      isModalMediumOpen: false,
      isModalLargeOpen: false
    };
  }

  toggleModalSmall = (e) => {
    e.preventDefault();
    this.setState((prev) => ({
      ...prev,
      isModalSmallOpen: !prev.isModalSmallOpen
    }));
  };

  toggleModalMedium = (e) => {
    e.preventDefault();
    this.setState((prev) => ({
      ...prev,
      isModalMediumOpen: !prev.isModalMediumOpen
    }));
  };

  toggleModalLarge = (e) => {
    e.preventDefault();
    this.setState((prev) => ({
      ...prev,
      isModalLargeOpen: !prev.isModalLargeOpen
    }));
  };

  render() {
    return (
      <div className="container">
        <div className="row">
          <div className="col">
            <h1>Hello Y'all!</h1>
            <p className="yo-green">My Modal Samples</p>

            <div className="row mt-5">
              <div className="col">
                <button
                  className="btn btn-primary"
                  onClick={this.toggleModalSmall}
                >
                  Modal Small
                </button>
              </div>
              <div className="col">
                <button
                  className="btn btn-primary"
                  onClick={this.toggleModalMedium}
                >
                  Modal Medium
                </button>
              </div>
              <div className="col">
                <button
                  className="btn btn-primary"
                  onClick={this.toggleModalLarge}
                >
                  Modal Large
                </button>
              </div>
            </div>
          </div>
        </div>
        <ModalSmall
          modalName="smallModal"
          modalTitle="Small Modal"
          modalBody="This is the small modal!"
          toggleModal={this.toggleModalSmall}
          modalOpen={this.state.isModalSmallOpen}
        />
        <ModalMedium
          modalName="mediumModal"
          modalTitle="Medium Modal"
          modalBody="This is the medium modal!"
          toggleModal={this.toggleModalMedium}
          modalOpen={this.state.isModalMediumOpen}
        />
        <ModalLarge
          modalName="largeModal"
          modalTitle="Large Modal"
          modalBody="This is the LARGE modal!"
          toggleModal={this.toggleModalLarge}
          modalOpen={this.state.isModalLargeOpen}
        />
      </div>
    );
  }
}

One of the in-between components:

import React from "react";
import Modal from "./Modal";

const ModalSmall = (props) => {
  return (
    <Modal
      modalName={props.modalName}
      modalTitle={props.modalTitle}
      modalBody={props.modalBody}
      toggleModal={props.toggleModal}
      modalOpen={props.modalOpen}
    />
  );
};

export default ModalSmall;

Here is my modal Component:

import React from "react";

export default class Modal extends React.Component {
  componentDidUpdate() {
    if (this.props.modalOpen) {
      console.log("Open!", this.props.modalOpen);
      document.body.classList.add("drawer-open");
    } else {
      console.log("Closed!", this.props.modalOpen);
      document.body.classList.remove("drawer-open");
    }
  }

  render() {
    return (
      <div className="mymodal" id={this.props.modalName}>
        <div
          onClick={this.props.toggleModal}
          className={`mymodal-overlay ${this.props.modalOpen && "active"}`}
            ></div>
        <div
          className={`mymodal-content d-flex flex-column ${
            this.props.modalOpen && "active"
          }`}
        >
          <header className="p-2 border-bottom d-flex">
            <span
              className="material-icons clickable"
              onClick={this.props.toggleModal}
            >
              close
            </span>
            <div className="flex-grow-1 ml-2">{this.props.modalTitle}</div>
          </header>
          <div className="p-2 flex-grow-1">{this.props.modalBody}</div>
          <footer className="p-2 border-top">&copy; ChidoPrime 2021</footer>
        </div>
      </div>
    );
  }
}

Working Sample Here with Solution Applied

UPDATE! ————-

There is a second approach I would like to include, different than the checked answer offered by @sanishJoseph. In which I add a constructor and declare a state within the modal controller. Without the need of using React.PureComponent. I use preProvs within the componentDidUpdate. Code for the modal follows:

  constructor(props) {
    super(props);
    this.state = {
      modalOpen: false
    };
  }


  componentDidUpdate(prevProps) {
    if (prevProps.modalOpen === this.props.modalOpen) return;

    if (this.props.modalOpen) {
      console.log("Open!", this.props.modalOpen);
      document.body.classList.add("drawer-open");
    } else {
      console.log("Closed!", this.props.modalOpen);
      document.body.classList.remove("drawer-open");
    }
  }

Second Sample using prevProps without using React.PureComponent

Advertisement

Answer

I think the biggest mistake is in your Parent component. Your initial state of the page is

 this.state = {
          isModalSmallOpen: false,
          isModalMediumOpen: false,
          isModalLargeOpen: false
        }

But, when you open a Modal, you are setting your state to one item in the state, rest of the items are going null. Meaning, when you do

this.setState({
      isModalSmallOpen: !this.state.isModalSmallOpen
    })

You are setting isModalMediumOpen: null, isModalLargeOpen: null.

What you should be doing is,

this.setState((prev) => ({...prev,
          isModalSmallOpen: !prev.isModalSmallOpen
        }))

So all of your states will remain in your state. This change is needed in all the 3 modal opening functions.

Update :

Fix is petty easy. All you need to do is add a react.memo if it was a functional component. In your case make your Modal component as a PureComponent.

export default class Modal extends React.PureComponent

Pure Components in React are the components which do not re-renders when the value of state and props has been updated with the same values.

https://codesandbox.io/s/my-custom-modal-forked-yg4vo?file=/src/App.js

User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement