Material-ui dialog is not closing when overlay is clicked - reactjs

I'm using material-ui's dialog. When the overlay is clicked, handleClose is not called. When the "Esc" button is pressed, it is called.
I've injected tap events. What else is wrong?
import React, { Component, PropTypes } from 'react';
import Dialog from 'material-ui/Dialog';
import baseTheme from 'material-ui/styles/baseThemes/lightBaseTheme';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
// Tap event required
import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();
class MyComponent extends Component {
handleClose(){
console.log('I will be closed');
}
render() {
return (
<div>
<h1>Modal test</h1>
<MuiThemeProvider muiTheme={getMuiTheme()}>
<Dialog
title="Test Dialog"
modal={false}
open={(this.props.active)}
onRequestClose={this.handleClose}
autoScrollBodyContent={true}>
Hello world, I'm a dialogue.
</Dialog>
</MuiThemeProvider>
</div>
);
}
}

you must define onClose prop for dialog. as you can see:
const [opendialog , setOpendialog] = React.useState(false);
const handleOpen = () => {
setOpendialog(true);
};
const handleClose = () => {
setOpendialog(false);
}
return (
<>
<Button onClick={handleOpen}>open dialog!</Button>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>
<Typography>
are you sure?
</Typography>
</DialogTitle>
<Button onClick={handleClose}>No</Button>
</Dialog>
</>
);

You have to define a function for closing the dialog, and send it with prop onClose to Dialog component.
One thing you have to keep in mind, is that, props disableBackdropClick should not be passed to Dialog component.
The sample code is as follow
<Dialog
open = { props.open }
// disableBackdropClick
onClose = {(event, reason) => {
if (props.onClose) {
props.onClose(event, reason);
}
}}
>
{ ...props.children }
</Dialog>

According to specs you should not have to do this, but you can use the following property to force close :
<Dialog onBackdropClick={YOUR_CLOSE_FUNCTION_HERE} />

I just tried it out and it works fine. Is there any other code relevant to this? Maybe re-install material ui and try again.

Related

antd modal on modal click not opening

I have an antd modal as shown in the below code, Now when I select Create Manual and click Next, I want to close this modal and open another Modal2 but another modal is not getting opened after clicking next.
Here is my code. ( Codesandbox live demo - link )
Please suggest a workaround to get his second modal generated. Thanks
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Modal, Button, Radio } from "antd";
const App = () => {
const [isModalVisible, setIsModalVisible] = useState(false);
const [selectRadio, setselectRadio] = useState("preselect");
const showModal = () => {
setIsModalVisible(true);
};
const select = (e) => {
// you can save the value in here
setselectRadio(e.target.value);
console.log(e.target.value);
};
function modalclick() {
if (selectRadio === "preselect") {
alert("pre-select");
} else {
//---------------> UNABLE TO OPEN ANOTHER MODAL HERE <-------------------------------------
<Modal title="Create Test Suite" visible={isModalVisible}>
MODAL 2 COMES HERE
</Modal>;
alert("manual");
}
}
return (
<>
<Button type="primary" style={{ float: "right" }} onClick={showModal}>
Create Test Suite
</Button>
<Modal
title="Create Test Suite"
visible={isModalVisible}
footer={[
<Button key="cancel" onClick={() => setIsModalVisible(false)}>
Cancel
</Button>,
<Button type="primary" key="next" onClick={modalclick}>
Next
</Button>
]}
>
<Radio.Group
defaultValue="preselect"
buttonStyle="solid"
onChange={(e) => {
select(e);
}}
>
<Radio value="preselect">Create from Preselect</Radio>
<Radio value="manual">Create Manual</Radio>
</Radio.Group>
</Modal>
</>
);
};
ReactDOM.render(<App />, document.getElementById("container"));
To show the modal 2 you can use a useState hook or a useRef hook. In both methods, you need first to put this modal 2 in the return of your "App".
useState way: Just use a state to control the visibility, like how you do in modal 1, simple.
useRef way: This is a little more complex. You will need to use a useImperativeHandle inside the modal component, and create a function (inside too) to control the visibiliity. So, in your page, you can just call the function that is inside the component, to show the modal. Using this method, the logic about the state control of visibility leaves the page and goes into the component.
Modal2 is not visible because is out of the return of App function. Put Modal 2 inside return and try again.

Showing modal via method call in react app

In our react app (we use reactstrap), we've multiple pages from where a confirmation modal can be shown. We do not want to include the modal code in every page. Is there a way to do this programmatically by invoking a method?
We can use plain bootstrap modals directly in the public index.html and from the util method use dom selector and invoke the modal but want to avoid this. Any pointers on how to go about this?
If what you want is only one modal which can be used across multiple pages(instead of putting one modal in every page), you can put it in the root component usually names as App.
import Modal from "somewhere";
function App() {
const [modal, setModal] = useState(false);
return <>
<Model isOpen={modal} />
{/* If you need to toggle modal when clicking something in PageA, you can pass the prop down like this */}
<PageA onToggleModel={()=>{setModal(!modal)}} />
<PageB />
</>;
}
Just in case, doing import Modal from "somewhere" in every page wouldn't result in duplicate code in your final bundle. It's totally fine to do that.
Here's what we did.
Alert Component:
import React, { useState } from "react";
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
const Alert = props => {
const [modal, setModal] = useState(props.open ? props.open : true);
const toggle = () => {
setModal(!modal);
if (props.cb) {
props.cb();
}
if (props.reloadPage) {
window.location.reload();
}
};
return (
<div>
<Modal isOpen={modal} toggle={toggle}>
<ModalHeader toggle={toggle}>{props.title}</ModalHeader>
<ModalBody>{props.text}</ModalBody>
<ModalFooter>
<Button color="primary" onClick={toggle}>
Ok
</Button>
</ModalFooter>
</Modal>
</div>
);
};
export default Alert;
Util.js:
import React from "react";
import ReactDOM from "react-dom";
import Alert from "./Alert";
const Util = {
alert: (message, okCb, reload) => {
ReactDOM.render(
<Alert
title="Done"
text={message}
cb={() => {
ReactDOM.unmountComponentAtNode(
document.getElementById("modalHolder")
);
if (okCb) {
okCb();
}
}}
reloadPage={reload}
/>,
document.getElementById("modalHolder")
);
}
};
export default Util;
In index.html we created a dom element:
<div id="modalHolder"></div>
So to invoke the modal imperatively, call:
Util.alert("Data has been saved")

Handling icon onClick events with Material-UI and ReactJS

I'm trying to get the "name" of the icon button when clicked
I've read about using material UI's but I keep getting "undefined" from the handler
import React from 'react'
import {IconButton} from '#material-ui/core'
import InfoIcon from '#material-ui/icons/InfoOutlined'
const App = () => {
const handleIconClicks = (e) => {
console.log(e.target.name)
}
return (
<div>
<IconButton name="details" onClick={(e) => handleIconClicks(e)}>
<InfoIcon />
</IconButton>
</div>
)
}
export default App
handleIconClicks() should return the name of the event.target, instead I get undefined
event.target may refer to the icon in case of <IconButton />.
You should be able to safely use event.currentTarget to get the button (and event.currentTarget.name to get its name).
You could not get the name using the event of the IconButton because its event is null.
So you could it using this.
const handleIconClicks = name => () => {
console.log(name);
}
... ... ...
<IconButton name="details" onClick={handleIconClicks('detail')}>
<InfoIcon />
</IconButton>
... ... ...

OnClick Listeners not working after closing full-screen dialog using react-material-ui

I have a problem with a fullscreen dialog that is being closed when the associated "OnClose" function is called. The dialog closes, however i cannot be opened again.
Any idea on why this happens? It feels like there is an invisible dialog staying on the canvans that prevents event from being bubbled to the button, or something similar.
import React from "react";
import Button from "#material-ui/core/Button";
import Dialog from "#material-ui/core/Dialog";
import "./FooterBar.css";
import Slide from "#material-ui/core/Slide";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import IconButton from "#material-ui/core/IconButton";
import CloseIcon from "#material-ui/icons/Close";
class BarItem extends React.Component {
constructor(props) {
super(props);
this.state = {
title: props.title,
targetURL: props.targetURL,
dialogOpen: false
};
this.barItemClicked = this.barItemClicked.bind(this);
this.handleClose = this.handleClose.bind(this);
}
barItemClicked() {
this.setState({
dialogOpen: true
});
}
handleClose() {
this.setState({
dialogOpen: false
});
}
render(props) {
const Transition = React.forwardRef(function Transition(props, ref) {
return <Slide direction="up" ref={ref} {...props} />;
});
return (
<div>
<Button onClick={this.barItemClicked}>{this.state.title}</Button>
<Dialog
fullScreen
open={this.state.dialogOpen}
onClose={this.handleClose}
TransitionComponent={Transition}
>
<AppBar>
<Toolbar>
<IconButton
edge="start"
color="inherit"
onClick={this.handleClose}
aria-label="Close"
>
<CloseIcon />
</IconButton>
</Toolbar>
</AppBar>
</Dialog>
</div>
);
}
}
class FooterBar extends React.Component {
render() {
return (
<div class="footerbar">
<BarItem title="Impressum" targetURL="a" />
<BarItem title="Datenschutzerklärung" targetURL="b" />
<BarItem title="Kontakt" targetURL="c" />
</div>
);
}
}
export default FooterBar;
I expect the buttons of the Footerbar to re-open the dialog, but this does not happen.
It looks like the problem lies in your TransitionComponent, you're passing a new instance of it to your Dialog each time you render. Try declaring it outside of your BarItem class.
Also, depending on what you want to display in your modal, I would find it better to put the modal and handler in your FooterBar component. Take a look at this sandbox that I created from your code. Maybe it'll give you some ideas.
Let me know if it helps.

Material UI ClickAwayListener close when clicking itself

I have the below display sidebar Switch that shows up within a Popper. So, Ideally, if you click elsewhere (outside of the Popper element), Popper should disappear. If you click inside Popper element, it should not go anywhere. When I click on the Switch or Display Sidebar text, that Popper goes away. I wrapped the Popper with <div> it didn't help either.
Popper https://material-ui.com/api/popper/
Switch https://material-ui.com/api/switch/
ClickAwayListener https://material-ui.com/utils/click-away-listener/
Below is the Popper Code
<ClickAwayListener onClickAway={this.handleClickAway}>
<div>
<Popper className={classes.popper} id={id} open={open} placement="bottom-end" anchorEl={anchorEl} transition>
{({ TransitionProps }) => (
<Fade {...TransitionProps} timeout={350}>
<Paper className={classes.SliderBox}>
<Switch
checked={this.state.checkedB}
onChange={this.handleChange('checkedB')}
value="checkedB"
color="primary"
onClick={handleDrawer}
className={classNames(classes.menuButton, sidebar && classes.hide)}
/>
Display Sidebar
</Paper>
</Fade>
)}
</Popper>
</div>
</ClickAwayListener>
I have the sample here (though I couldn't get it work I don't know why it gives error on clickaway)
https://codesandbox.io/s/8pkm3x1902
By default, Popper leverages a portal (https://github.com/mui-org/material-ui/blob/v4.11.0/packages/material-ui/src/Popper/Popper.js#L202) which renders its content in a separate part of the DOM (as a direct child of the <body> element) from where the Popper element is. This means that you have the ClickAwayListener around an empty <div>, so a click anywhere (including within the Popper content) will be considered to be outside of that empty <div>.
Moving the ClickAwayListener directly around the Fade (rather than around the Popper) ensures that it is surrounding the actual content rendered by the Popper.
Here's a working example based on your sandbox:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import Popper from "#material-ui/core/Popper";
import Fade from "#material-ui/core/Fade";
import Paper from "#material-ui/core/Paper";
import Switch from "#material-ui/core/Switch";
import Avatar from "#material-ui/core/Avatar";
import ClickAwayListener from "#material-ui/core/ClickAwayListener";
class App extends Component {
state = {
anchorEl: null,
open: false,
checkedB: true
};
handleClick = (event) => {
const { currentTarget } = event;
this.setState((state) => ({
anchorEl: currentTarget,
open: !state.open
}));
};
handleChange = (name) => (event) => {
this.setState({
[name]: event.target.checked
});
};
handleClickAway = () => {
this.setState({
open: false
});
};
render() {
const { anchorEl, open } = this.state;
const { handleDrawer } = this.props;
const id = open ? "simple-popper" : null;
return (
<div className="App">
asdsadsa
<Avatar
alt="Test"
src="https://www.nretnil.com/avatar/LawrenceEzekielAmos.png"
style={{ margin: "0 10px" }}
onClick={this.handleClick}
/>
<div>
<Popper
id={id}
open={open}
placement="bottom-end"
anchorEl={anchorEl}
transition
>
{({ TransitionProps }) => (
<ClickAwayListener onClickAway={this.handleClickAway}>
<Fade {...TransitionProps} timeout={350}>
<Paper //className={classes.SliderBox}
>
<Switch
checked={this.state.checkedB}
onChange={this.handleChange("checkedB")}
value="checkedB"
color="primary"
onClick={handleDrawer}
/>
Display Sidebar
</Paper>
</Fade>
</ClickAwayListener>
)}
</Popper>
</div>
</div>
);
}
}
export default App;
ReactDOM.render(<App />, document.getElementById("root"));
In v4 the ClickAwayListener supports recognizing that elements are within its React element tree even when they are rendered within a portal (the original question was for v3), but it is still more reliable to put the ClickAwayListener inside the Popper rather than outside to avoid the ClickAwayListener firing on the click to open the Popper which then makes it look like the Popper isn't working since it immediately closes (example: https://codesandbox.io/s/popper-clickawaylistener-forked-x4j0l?file=/src/index.js).
For me, just making the popupOpen=false not solved the problem. So, I have to set the anchorElement to null as well, like so
const handleClickAway= ()=>{
popupOpen=false; // Here you can also use state hook, but in my case, I'm using simpe boolean variable.
setAnchorEl(null);
}

Resources