I'm moving away from using DevExtreme/DevExpress components, and the last one to go is my Popup / Dialog component.
The way the old popup worked, is that it was rendered in the background. The popup is rendered, but hidden, until a visible attribute is passed to it. I had it coded as the following:
<UniversalPopupComponent
popupDefinition={ownerDatabaseView}
popupValues={databaseRowData}
onSubmit={this.myPopupSubmitted}
onCancel={this.myPopupCancelled}
visible={this.state.popupVisibility}
/>
Material UI component does not seem to work the same way. For starters, how do I tell MaterialUI Dialog to only show when I click a button where the Dialog component is called, before I get to the part of actually adding data and processing data that is passed inside to generate the content?
I currently have the MUI progress (PopupMUI.js) :
class UniversalPopupComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
open: this.props.open
}
}
handleClickClose = () => {
this.setState({open: false})
}
render() {
return (
<Dialog open={this.state.open} onClose={this.handleClickClose}>
<DialogContent>
placeholder
</DialogContent>
<DialogActions>
<Button onClick={this.handleClickClose}> Close </Button>
<Button onClick={this.handleClickClose}> Submit </Button>
</DialogActions>
</Dialog>
)
}
}
export default UniversalPopupComponent;
and in App.js contains the following related things:
class App extends React.Component {
state = {
open: false,
};
handlePopupOpen = () => {
this.setState({open:true})
}
render() {
return (
<div> ...
<Button onClick={this.handlePopupOpen}> Open Menu </Button>
<UniversalPopupComponent open={this.state.open} />
... </div>
)}
I cannot get the Dialog to open this way, by passing the state of 'open' as 'true' to the Dialog inside the component with props.
However, if I have the below, then Dialog opens fine and closes fine too (click close is defined as just setState({open:false})
<Button onClick={this.handlePopupOpen}> Open Menu </Button>
<Dialog open={this.state.open} onClose={this.handleClickClose}>
<DialogContent>
placeholder
</DialogContent>
<DialogActions>
<Button onClick={this.handleClickClose}> Close </Button>
<Button onClick={this.handleClickClose}> Submit </Button>
</DialogActions>
</Dialog>
I have not set up to pass any data yet, as the big problem is that the dialog won't open for me to start setting everything up and re-writing parts.
So my stupid brain completely forgot that, once something is in constructor, it does not get updated. So I was passing the open prop, but only writing it once.
I simply added a componentDidUpdate that compares current state of Open to new state of Open from prop, if they are different, write new state of prop.
Then I added a callback, so that when something is closed, it sets the state of open to false, which updates the component and closes the menu.
Related
I am using React v18.1, react-bootstrap v2.4. I have a Modal component I am trying to get to display upon a button press. The modal component is quite simple:
class AdjustmentModal extends React.Component {
constructor(props) {
super(props);
this.state = {
'show': this.props.show
};
this.handleClose = this.handleClose.bind(this);
}
handleClose() {
this.setState({ show: false })
}
render() {
return (
<Modal show={this.state.show} onHide={this.handleClose}>
[ ... Modal Content Here ... ]
</Modal>
);
}
}
export default AdjustmentModal;
As you can see, I bind the modal's show property to the value of show in state.
Then, in the component in which I want to display my modal, I have the following:
// Within render() ...
<AdjustmentModal
show={this.state.showAdjustment}
partNo={this.state.partNo}
onHandQty={this.state.onHandQty}
/>
// Futher on in the code, display the modal on click:
<Button className="icon" onClick={this.handleDisplayAdjustment}>
<i className="bi bi-pencil-square"></i>
</Button>
handleDisplayAdjustment :
handleDisplayAdjustment(event) {
event.preventDefault();
this.setState({
showAdjustment : true
});
}
Now, despite the value showAdjustment in the parent component changing to true, the modal doesn't display.
I could set the <Modal show={this.props.show} .../> instead, but props are read-only, so there is no way to close the modal again if reading from props rather than state.
You can use props, which is a better way to handle this if you want to close it then pass a method from the parent which when called update the state in the parent to false and due state update the parent component will re render and though the child component that is the modal component and the Modal will get the updated value which will be false. below is the code on how you can achieve that.
closeModal() {
this.setState({
showAdjustment: false
})
}
// Within render() ...
<AdjustmentModal
show={this.state.showAdjustment}
partNo={this.state.partNo}
onHandQty={this.state.onHandQty}
onClose={this.closeModal.bind(this)}
/>
// Futher on in the code, display the modal on click:
<Button className="icon" onClick={this.handleDisplayAdjustment}>
<i className="bi bi-pencil-square"></i>
</Button>
For the child component
class AdjustmentModal extends React.Component {
handleClose() {
this.props.onClose()
}
render() {
return (
<Modal show={this.props.show} onHide={this.handleClose}>
[ ... Modal Content Here ... ]
</Modal>
);
}
}
export default AdjustmentModal;
EDIT: Explaining the approach
This will make your Modal component a Controlled component that is controlled by Parent, also updating props as a state inside the child component is not the right way, which may create potential bugs.
I have passed a state as a prop from a functional component to a fullscreen dialog component, how can I trigger onClose or onClick to close after the dialog is displayed
const [openLetterOnlyDialog, SetOpenLetterOnlyDialog] = React.useState(false);
<LetterOnlyDialog open={openLetterOnlyDialog}/>
**Dialog component to be opened**
export default function LettersOnly(props) {
return (
<div>
<Dialog fullScreen open={props.open} onClose={handleClose} TransitionComponent={Transition}>
</Dialog>
Following your example, add this function in your component where you are declaring the LettersOnly dialog. Pass it the same way you are passing the open prop. It will handle the close event of your dialog.
function handleClose() {
SetOpenLetterOnlyDialog(false);
}
Then the components would look more or less like this:
<LetterOnlyDialog open={openLetterOnlyDialog} close={handleClose} />
<Dialog
fullScreen
open={props.open}
onClose={props.close}
TransitionComponent={Transition}
></Dialog>
I have a React app with modal, that pop-ups with rules of the game when one clicks a button. What I want to do is make it so when I click anywhere outside this pop up window it will close. i have three files. app.js, dialog.js, and outsidealerter.js . In my main app.js when I click a button it sets a state to visible, so my element takes it and renders based upon it. my outsideralerer.js basicly detects if there is a click outside anything wrapped with specific tags. Now the problem comes that i have a method that changes the state of visibility in app.js, so in order for outsderalerter.js to use it, I pass it to it so it can have access to my main state and change it so that when a click is outside the zone the pop up window disappears. Kind of works except it closes it down even if i click within a pop up window, because when i pass the value to outsidealerter it considers the whole body as a no click zone. My question is how can I prevent it from triggering and just pass it a value, or is it possible to change the state value of app.js from outsidealerter.js
App.js
updateState() {
this.setState({ isOpen: false });
}
<div id='rule-button'>
<button onClick={(e)=>this.setState({isOpen : true})} id="modalBtn" class="button">Open Rules</button>
</div>
<OutsideAlerter updateParent={ this.updateState.bind(this)}/>
<Dialog isOpen={this.state.isOpen} onClose={(e)=>this.setState({isOpen : false})}>
</Dialog>
outsidealerter.js
handleClickOutside(event) {
if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
//alert('You clicked outside of me!');
{this.props.updateParent()};
}
}
I think it will be simpler to have the modal take the full space of the window height and width and just make it invisible except for the content of what you want to show.
We can wrap the modal with onClick={hideModal} and wrap the inner content with onClick={e => e.stopPropagation()} which will prevent our wrapper for triggering the hideModal handler.
class ModalWrapper extends React.Component {
state = { isModalOpen: true };
toggleModal = () => {
this.setState(({ isModalOpen }) => ({
isModalOpen: !isModalOpen
}));
};
render() {
const { isModalOpen } = this.state;
return (
<div className="App">
<button onClick={this.toggleModal}>Open Modal</button>
{isModalOpen && <Modal hideModal={this.toggleModal} />}
</div>
);
}
}
function Modal({ hideModal }) {
return (
<div onClick={hideModal} className="modal">
<div onClick={e => e.stopPropagation()} className="modal__content">
Modal content
</div>
</div>
);
}
Working example
Currently, I have a view which shows all documents from the database.
Each one has a button which triggers the document to be deleted from the database.
Ideally, I'd like to open a modal message (using Materialize) to ensure deletion should happen.
I know that I can have a modal for each of the elements but that seems redundant and too much to add. I'd like the flow to go from:
button -> delete
to:
button -> confirm -> delete
I'd like this to happen by changing the onClick of the button to open a modal and be able to pass the action of the confirm button through
The button code currently looks like:
<button
className="btn"
onClick={() => {handleClick(category._id);}}
>
Delete
</button>
The current onClick handler for each button is the following:
const handleClick = id => {
this.props.deleteCategory(id);
};
I'm new to React/Redux and Materialize so any help would be appreciated and if any more information is required, please let me know :)
Thanks,
James
Okay, so I found the solution myself and since I couldn't find much else online on how to do this, I thought I'd share the solution too.
So, I installed react-materialize first and created a component to hold the modal and render the modal.
I replaced the button click function with a function which updates the state of the current component like so:
const handleClick = category => {
this.setState({
categoryClicked: category,
modalOpen: true
});
};
I then tied the component containing the model to this state using props like so:
<ConfirmDeletion
onClickYes={null}
onClickNo={null}
name={this.state.categoryClicked.name}
open={this.state.modalOpen}
actions={[
<button
onClick={modalNo}
className="modal-close waves-effect waves-green btn-flat"
>
No
</button>,
<button
onClick={modalYes}
className="modal-close waves-effect waves-green btn-flat"
>
Yes
</button>
]}
/>
Note that the actions are a list of JSX elements which should be the buttons on the modal.
I also created the click event handlers to update the state (and remove from the database in the "yes" case) as follows:
const modalYes = () => {
this.props.deleteCategory(this.state.categoryClicked._id);
this.setState({
modalOpen: false
});
};
const modalNo = () => {
this.setState({
modalOpen: false
});
};
and just for completeness, here's the component wrapping the modal in case it's useful to anyone in the future:
import React, { Component } from "react";
import { Modal } from "react-materialize";
class ConfirmDeletion extends Component {
render() {
const { name, open, actions } = this.props;
return (
<Modal
id="confirmDeletion"
open={open}
actions={actions}
header="Are you sure?"
>
<div className="modal-content">
<p>
Are you sure you want to do delete '{name}'? It cannot be undone.
</p>
</div>
</Modal>
);
}
}
export default ConfirmDeletion;
Based on Materialize Documentation you should follow these steps :
1- Create your modal structure. for example :
<!-- Modal Structure -->
<div id="modal1" class="modal">
<div class="modal-content">
<h4>Modal Header</h4>
<p>A bunch of text</p>
</div>
<div class="modal-footer">
Agree
</div>
</div>
2- Initialize the modal :
document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('.modal');
var instances = M.Modal.init(elems, options);
});
Note: remove options if you don't want to add modal options. If don't it causes an error.
3- Change handleClick function to open modal :
const handleClick => {
//var elem = get modal element here
var instance = M.Modal.getInstance(elem);
instance.open();
};
4- Finally, call a function when user click on agree to delete the category. You can add disagree button to close modal with :
instance.close();
I'm working with React MDL library, and I used pre-defined components like FABButton
<FABButton>
<Icon name="add"/>
</FABButton>
And it shows the button as in the image bellow:
Now, what I want is showing a dialog with the + icon... not as what happens here:
This happened after this code:
<FABButton>
<AddingProject />
<Icon name="add" />
</FABButton>
The class of dialog is as follows:
class AddingProject extends Component {
constructor(props) {
super(props);
this.state = {};
this.handleOpenDialog = this.handleOpenDialog.bind(this);
this.handleCloseDialog = this.handleCloseDialog.bind(this);
}
handleOpenDialog() {
this.setState({
openDialog: true
});
}
handleCloseDialog() {
this.setState({
openDialog: false
});
}
render() {
return (
<div>
<Button colored onClick={this.handleOpenDialog} raised ripple>
Show Dialog
</Button>
<Dialog open={this.state.openDialog} onCancel={this.handleCloseDialog}>
<DialogTitle>Allow data collection?</DialogTitle>
<DialogContent>
<p>
Allowing us to collect data will let us get you the information
you want faster.
</p>
</DialogContent>
<DialogActions>
<Button type="button">Agree</Button>
<Button type="button" onClick={this.handleCloseDialog}>
Disagree
</Button>
</DialogActions>
</Dialog>
</div>
);
}
}
export default AddingProject;
The above code is with the required import statements
This works with me....
First step: I added the component of the modal as follows:
<FABButton>
<Icon name="add" />
</FABButton>
<ProjectModal>
Second step: I added this prop: visible for the component as here:
<ProjectModal visible={this.state.showDialog} />
And here you need to add showDialog to the states in your class with false.
state = {
showDialog: false
};
Now, to step 3.
Third step: Add this part to your code, to be called when you click.
openModal = () => {
this.setState({ showDialog: true });
};
On the other side, you need to implement onClick in the button as follows:
<FABButton onClick={this.openModal.bind(this)}>
<Icon name="add" />
</FABButton>
Fourth step: In the modal/dialog class, you need to store the visible in a new state variable, which is here showDialogModal
constructor(props, context) {
super(props, context);
this.state = {
showDialogModal: this.props.visible
};
}
Now, you need to pass the changed state from the first class to the modal/dialog class, there are more than one option that React gives you, I used this one in fifth step. Fifth step: use this React event componentWillReceiveProps as below.
componentWillReceiveProps(nextProps) {
if (this.props.showDialogModal != nextProps.visible) {
this.setState({
showDialogModal: nextProps.visible
});
}
}
This will reflect any change in visible property from the first class to our new one here which is showDialogModal
Now in the render part, you need to check the docs of your components, here I started with React-Bootstrap.
Sixth step: use the show property in your component.
<Modal show={this.state.showDialogModal} onHide={this.closeModal}>
onHide is for closing the dialog, which makes you need to implement this too.
closeModal = () => {
this.setState({ showDialogModal: false });
};
Finally, in the closing button, add this:
<Button onClick={this.closeModal.bind(this)}>Close</Button>
Good luck.