Reputation: 4624
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
Upvotes: 0
Views: 893
Reputation: 2256
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
Upvotes: 1
Reputation: 434
The code is a little complex to understand, but I think the main problem is with the logic used to implement it. If I understood correctly you are using the same Component more than once. So, each component executes componentDidUpdate
method each time that is rerendered.
What this means is that if you are toggling one of your modals in the "parent" component with the methods "toggleModal..." then, the parent render method is executed and it executes each render children method. What happened there is that with your first modal you are adding o removing the body css, with the second you are doing the inverse and with the third, you are adding and removing again.
You have a lot of things to get better there, but the most simple is use the arguments you got in your componentDidUpdated
method and make sure you only executed your code if the new props changes. This going to solve your problem.
Upvotes: 0