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">© 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