may somebody please help me customize/disable the Material-UI Dialog's touch mechanics? I have the Cancel and Confirm buttons, and that's just what I want to use to close the dialog. However, making a selection from a dropdown menu caused the dialog to autoclose according to the doc. And I couldn't see how to do that in the doc itself.
The problem
The problem I have is that the auto-closing is doing the job I want the cancel button to do. i.e: Closing the dialog and empty an array.
I'd appreciate your help.
Any special reason to use the Material-UI Simple Dialog?
From your question, seems like a Confirmation dialog is what you're looking for :)
Simple dialog touch mechanism
Touch mechanics:
Choosing an option immediately commits the option and closes the menu
Touching outside of the dialog, or pressing Back, cancels the action and closes the dialog
In the other hand, matching your needs:
Confirmation dialogs
Confirmation dialogs require users to explicitly confirm their choice before an option is committed. For example, users can listen to multiple ringtones but only make a final selection upon touching “OK”.
Touching “Cancel” in a confirmation dialog, or pressing Back, cancels the action, discards any changes, and closes the dialog.
import React from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemText from '#material-ui/core/ListItemText';
import DialogTitle from '#material-ui/core/DialogTitle';
import DialogContent from '#material-ui/core/DialogContent';
import DialogActions from '#material-ui/core/DialogActions';
import Dialog from '#material-ui/core/Dialog';
import RadioGroup from '#material-ui/core/RadioGroup';
import Radio from '#material-ui/core/Radio';
import FormControlLabel from '#material-ui/core/FormControlLabel';
const options = [
'None',
'Atria',
'Callisto',
'Dione',
'Ganymede',
'Hangouts Call',
'Luna',
'Oberon',
'Phobos',
'Pyxis',
'Sedna',
'Titania',
'Triton',
'Umbriel',
];
function ConfirmationDialogRaw(props) {
const { onClose, value: valueProp, open, ...other } = props;
const [value, setValue] = React.useState(valueProp);
const radioGroupRef = React.useRef(null);
React.useEffect(() => {
if (!open) {
setValue(valueProp);
}
}, [valueProp, open]);
const handleEntering = () => {
if (radioGroupRef.current != null) {
radioGroupRef.current.focus();
}
};
const handleCancel = () => {
onClose();
};
const handleOk = () => {
onClose(value);
};
const handleChange = (event) => {
setValue(event.target.value);
};
return (
<Dialog
disableBackdropClick
disableEscapeKeyDown
maxWidth="xs"
onEntering={handleEntering}
aria-labelledby="confirmation-dialog-title"
open={open}
{...other}
>
<DialogTitle id="confirmation-dialog-title">Phone Ringtone</DialogTitle>
<DialogContent dividers>
<RadioGroup
ref={radioGroupRef}
aria-label="ringtone"
name="ringtone"
value={value}
onChange={handleChange}
>
{options.map((option) => (
<FormControlLabel value={option} key={option} control={<Radio />} label={option} />
))}
</RadioGroup>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleCancel} color="primary">
Cancel
</Button>
<Button onClick={handleOk} color="primary">
Ok
</Button>
</DialogActions>
</Dialog>
);
}
ConfirmationDialogRaw.propTypes = {
onClose: PropTypes.func.isRequired,
open: PropTypes.bool.isRequired,
value: PropTypes.string.isRequired,
};
const useStyles = makeStyles((theme) => ({
root: {
width: '100%',
maxWidth: 360,
backgroundColor: theme.palette.background.paper,
},
paper: {
width: '80%',
maxHeight: 435,
},
}));
export default function ConfirmationDialog() {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const [value, setValue] = React.useState('Dione');
const handleClickListItem = () => {
setOpen(true);
};
const handleClose = (newValue) => {
setOpen(false);
if (newValue) {
setValue(newValue);
}
};
return (
<div className={classes.root}>
<List component="div" role="list">
<ListItem button divider disabled role="listitem">
<ListItemText primary="Interruptions" />
</ListItem>
<ListItem
button
divider
aria-haspopup="true"
aria-controls="ringtone-menu"
aria-label="phone ringtone"
onClick={handleClickListItem}
role="listitem"
>
<ListItemText primary="Phone ringtone" secondary={value} />
</ListItem>
<ListItem button divider disabled role="listitem">
<ListItemText primary="Default notification ringtone" secondary="Tethys" />
</ListItem>
<ConfirmationDialogRaw
classes={{
paper: classes.paper,
}}
id="ringtone-menu"
keepMounted
open={open}
onClose={handleClose}
value={value}
/>
</List>
</div>
);
}
you can have a look of the working dialog here:
https://4zgol.csb.app/
Hope it helps and if not, feel free to explain more about the problem or even add a code snippet :)
Related
Merry Christmas at first to all of you!
[React + TypeScript]
And yes, I'm a newbie in react, I'm more kind of backend geek, but we all need to learn new stuff :)
I am trying to make my snackbar work in all components globally, to not write a new one every time.
I saw one post about this before: How to implement material-ui Snackbar as a global function?
But unfortunatelly I can't make it work.
Is my function even correct?
And if it is, how can I call it from another component now?
I made this function:
SnackbarHOC.tsx
`
import { AlertTitle, IconButton, Snackbar } from '#mui/material';
import Slide from '#mui/material';
import Alert from '#mui/material';
import { useState } from 'react';
import CloseIcon from '#mui/icons-material/Close';
function SnackbarHOC<T>(WrappedComponent: React.ComponentType<T>, Alert: React.ElementType) {
const [open, setOpen] = useState(false);
const [message, setMessage] = useState("I'm a custom snackbar");
const [duration, setDuration] = useState(2000);
const [severity, setSeverity] = useState(
"success"
); /** error | warning | info */
return (props: T) => {
const showMessage = (message: string, severity = "success", duration = 2000) => {
setMessage(message);
setSeverity(severity);
setDuration(duration);
setOpen(true);
};
const handleClose = (event: React.SyntheticEvent | Event, reason?: string) => {
if (reason === 'clickaway') {
return;
}
}
return (
<>
<WrappedComponent {...props} snackbarShowMessage={showMessage} />
<Snackbar
anchorOrigin={{
vertical: "bottom",
horizontal: "center"
}}
autoHideDuration={duration}
open={open}
onClose={handleClose}
//TransitionComponent={Slide}
>
<Alert severity="error"
sx={{ width: '100%' }}
action={
<IconButton
aria-label="close"
color="inherit"
size="small"
onClick={handleClose}>
<CloseIcon fontSize="inherit" />
</IconButton>
}>
{message}
</Alert>
</Snackbar>
</>
);
};
};
export default SnackbarHOC;
`
I tried to call it from another component, but I have no idea how to show the actual snackbar now :(.
It's all that is not giving me errors right now:
`
import SnackbarHOC from '../../SnackbarHOC';
`
I found a solution for that but in JS basically, it's the same I guess it's just not typed. I'm implementing within NEXTJS latest
//index.jsx
import Head from 'next/head'
import { useState } from 'react';
import {Button} from '#mui/material'
import Notification from '../components/Notification.component'
//create the jsx file with the Snackbar component implemented
function Home() {
// we need a simple hook for the button
const [open, setOpen] = useState(false);
}
const handleClick = () => {
setOpen(true);
};
const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
};
return(
<div >
<Head>
<title>HOME</title>
<meta name="description" content="AWESOME" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Button variant="outlined" onClick={handleClick}>SEND</Button>
{/*We can define new props for our component I'm mixin props from mui Alert and Snackbar, from Alert I'm using severity and from Snackbar the open and onClose and message goes inside the alert as a simple string :D */}
<Notification
open={open}
onClose={handleClose}
severity="error"
message="that is what I am talking about"/>
</div>
)
}
Now we just need to create the custom component that has multiple lil components inside that we can edit as we want!
//Notification.component.jsx file
import React, { forwardRef} from 'react';
import Snackbar from '#mui/material/Snackbar';
import MuiAlert from '#mui/material/Alert';
const Alert = forwardRef(function Alert(props, ref) {
return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />;
});
const Notification= ({open, onClose, severity, message}) => {
/*<> ... </> The empty tag is shorthand for <React. Fragment>, allowing you to have multiple top-most elements without wrapping further HTML.
*/
return (
<>
<Snackbar open={open} autoHideDuration={6000} onClose={onClose}>
<Alert onClose={onClose} severity={severity} sx={{ width: '100%' }}>
{message}
</Alert>
</Snackbar>
</>
);
}
export default Notification;
But if you really want use typed values You can check the returned type in your VSCode for each variable and function. but I really don't see any benefit for that. But as it doesn't change the result I will not implement it for me neither here.
I highly recommend to watch this video series https://www.youtube.com/watch?v=Roxf91mp3kw&t=367s
The goal is to open a delete option modal from a material ui dropdown menu.
The first step towards that is understanding how to simply select an item from a dropdown, and then trigger some form of action (opening the modal) depending on which item is selected.
The material UI docs all seem to offer samples in which onClick simply handles the closing of the menu. It seems there are rare examples of selecting something from a dropdown and then opening/doing things from there?
I'm having trouble seeing where I would insert the logic/event handling to handle 'if the user selects option x, open modal regarding option x' within the context of a material ui menu.
Here's my code to show the rabbit hole I'm down currently:
import * as React from 'react';
import IconButton from '#mui/material/IconButton';
import Menu from '#mui/material/Menu';
import MenuItem from '#mui/material/MenuItem';
import { useState } from 'react'
import Select from '#mui/material/Select';
interface IProps extends Omit<unknown, 'children'> {
children: any;
options: string[];
}
const ITEM_HEIGHT = 48;
const DropdownMenu = ({ children, options }: IProps) => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const [value, setValue] = useState('');
const handleChange = (event: any) => {
setValue(event.target.value);
console.log(value)
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<div>
<IconButton
aria-label="more"
id="long-button"
aria-controls="long-menu"
aria-expanded={open ? 'true' : undefined}
aria-haspopup="true"
onClick={handleClick}
>
{children}
</IconButton>
<Menu
id="long-menu"
MenuListProps={{
'aria-labelledby': 'long-button'
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
PaperProps={{
style: {
maxHeight: ITEM_HEIGHT * 4.5,
width: '20ch'
}
}}
>
<Select value={value} onChange={handleChange}>
{options.map(option => (
<MenuItem key={option} onClick={handleClose} >
{option}
</MenuItem>
))}
</Select>
</Menu>
</div>
);
};
export default DropdownMenu;
How would I use the dropdown to trigger the invocation of a Modal component based on a string value?
If I understand your question right then this should be the solution. simply assign a ref to the triggering element (in this case it's the IconButton). then you can open the menu via setting the open state. BTW why should the button render children?
import * as React from 'react';
import IconButton from '#mui/material/IconButton';
import Menu from '#mui/material/Menu';
import MenuItem from '#mui/material/MenuItem';
import { FC, useState } from 'react';
import Select from '#mui/material/Select';
type DropdownMenuProps = {
options: string[];
};
const ITEM_HEIGHT = 48;
const DropdownMenu: FC<DropdownMenuProps> = ({ children, options }) => {
const anchorEl = React.useRef<null | HTMLButtonElement>(null);
const [open, setOpen] = useState<boolean>(false);
const handleClick: React.MouseEventHandler = () => {
setOpen(true);
};
const [value, setValue] = useState('');
const handleChange: React.ChangeEventHandler<HTMLSelectElement> = (event) => {
const value = event.target.value;
setValue(value);
if (value === 'the value you want to trigger the menu') {
setOpen(true);
}
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<IconButton
aria-label="more"
id="long-button"
aria-controls="long-menu"
aria-expanded={open ? 'true' : undefined}
aria-haspopup="true"
onClick={handleClick}>
{children}
</IconButton>
<Menu
id="long-menu"
MenuListProps={{
'aria-labelledby': 'long-button',
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
PaperProps={{
style: {
maxHeight: ITEM_HEIGHT * 4.5,
width: '20ch',
},
}}>
<Select value={value} onChange={handleChange}>
{options.map((option) => (
<MenuItem key={option} onClick={handleClose}>
{option}
</MenuItem>
))}
</Select>
</Menu>
</div>
);
};
export default DropdownMenu;
I followed the material UI snack bar for a simple snackbar example. But instead of having a separate button to display snackbar message I want the message to appear when my existing button is clicked.
SnackBarTest.tsx --> ref Reference Link
import React from 'react';
import Button from '#material-ui/core/Button';
import Snackbar from '#material-ui/core/Snackbar';
import IconButton from '#material-ui/core/IconButton';
import CloseIcon from '#material-ui/icons/Close';
export default function SnackBarTest() {
const [open, setOpen] = React.useState(false);
const handleClick = () => {
setOpen(true);
};
const handleClose = (event: React.SyntheticEvent | React.MouseEvent, reason?: string) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
};
return (
<div>
<Button onClick={handleClick}>Open simple snackbar</Button>
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={open}
autoHideDuration={6000}
onClose={handleClose}
message="Note archived"
action={
<React.Fragment>
<Button color="secondary" size="small" onClick={handleClose}>
UNDO
</Button>
<IconButton size="small" aria-label="close" color="inherit" onClick={handleClose}>
<CloseIcon fontSize="small" />
</IconButton>
</React.Fragment>
}
/>
</div>
);
}
Now inside my sample.tsx file
import SnackBarTest from './SnackBarTest';
class Sample extends React.Component {
submitButton(){
//doing some work
}
render(){
return(
<div>
<Button onClick={this.submitButton}>Submit<Button/>
<SnackBarTest />
</div>
)
}
Inside my sample.tsx It will show my existing button and second button for the snack bar. How am I able to move the SnackBarTest button functionality into my existing Button?
I have been trying to create a component per function in my app, but I am facing the following issue.
I have the component DisplayAllData that sends the data and an actionable button to DisplayDataWithButton, the issue is that when someone clicks on the Button send in the props, the function modifies the state of the parent component, which is also sent as a parameter to FullScreenDialog, and that throws a Warning: Cannot update a component while rendering a different component.
I designed the components in this particular way because:
DisplayAllData is the only function that has the data to render and the actionable button. (Model)
DisplayDataWithButton only renders the data and displays the actionable components for that particular data, in this case a button that opens a Dialog in screen. (Viewer)
You can find a running example here: https://codesandbox.io/s/material-demo-forked-8oyef
import React from "react";
import Button from "#material-ui/core/Button";
import DisplayDataWithButton from "./DisplayDataWithButton";
import FullScreenDialog from "./fullscreendialog";
export default function App(props) {
const [openFullScreen, setopenFullScreen] = React.useState(false);
var items = ["John", "Melinda"];
var dataDisplayFunction = (data) => {
return data.map((item) => {
return [
item,
<Button
color="success"
size="small"
className="px-2"
variant="contained"
onClick={setopenFullScreen()}
>
Show Dialog
</Button>
];
});
};
return (
<>
<DisplayDataWithButton
shapeDataFunction={dataDisplayFunction}
data={items}
/>
<FullScreenDialog open={openFullScreen} />
</>
);
}
DisplayDataWithButton.js
export default function DisplayDataWithButton(props) {
return props.shapeDataFunction(props.data);
}
I suspect that there is another way to implement this model, any suggestion, or ideas on how to fix this one.
Thanks
A couple of things: "I have been trying to create a component per function in my app". Forget that - the pattern you have opted for here is called render props but I don't see how it is necessary. Keep it simple. If a big component is simpler to understand than a small component I always opt for the bigger component. Splitting your components will not magically make them easier to understand.
All of the warnings have been dealt with. Most of them were simple mistakes, for example: onClick={setopenFullScreen()} should be onClick={setopenFullScreen}. You can compare your sandbox with my sandbox for all of the changes.
import React from "react";
import ReactDOM from "react-dom";
import Button from "#material-ui/core/Button";
import FullScreenDialog from "./fullscreendialog";
export default function App() {
const [openFullScreen, setopenFullScreen] = React.useState(false);
const items = ["John", "Melinda"];
return (
<>
{items.map((item) => [
item,
<Button
key={item}
color="primary"
size="small"
className="px-2"
variant="contained"
onClick={() => setopenFullScreen((prev) => !prev)}
>
Show Dialog
</Button>
])}
<FullScreenDialog open={openFullScreen} />
</>
);
}
ReactDOM.render(<App />, document.querySelector("#root"));
import React from "react";
import { makeStyles } from "#material-ui/core";
import Button from "#material-ui/core/Button";
import Dialog from "#material-ui/core/Dialog";
import ListItemText from "#material-ui/core/ListItemText";
import ListItem from "#material-ui/core/ListItem";
import List from "#material-ui/core/List";
import Divider from "#material-ui/core/Divider";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import IconButton from "#material-ui/core/IconButton";
import Typography from "#material-ui/core/Typography";
import CloseIcon from "#material-ui/icons/Close";
import Slide from "#material-ui/core/Slide";
const useStyles = makeStyles((theme) => ({
appBar: {
position: "relative"
},
title: {
marginLeft: theme.spacing(2),
flex: 1
}
}));
const Transition = React.forwardRef(function Transition(props, ref) {
return <Slide direction="up" ref={ref} {...props} />;
});
export default function FullScreenDialog(props) {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Dialog
fullScreen
open={props.open}
onClose={handleClose}
TransitionComponent={Transition}
>
<AppBar className={classes.appBar}>
<Toolbar>
<IconButton
edge="start"
color="inherit"
onClick={handleClose}
aria-label="close"
>
<CloseIcon />
</IconButton>
<Typography variant="h6" className={classes.title}>
Sound
</Typography>
<Button autoFocus color="inherit" onClick={handleClose}>
save
</Button>
</Toolbar>
</AppBar>
<List>
<ListItem button>
<ListItemText primary="Phone ringtone" secondary="Titania" />
</ListItem>
<Divider />
<ListItem button>
<ListItemText
primary="Default notification ringtone"
secondary="Tethys"
/>
</ListItem>
</List>
</Dialog>
</div>
);
}
I am building a front-end application using React16 and the Material UI library.
I am trying to build a simple navigation bar at the top containing multiple menu items. I took the "simple menu" example from the material-ui.com website.
I tried to add a second menu item in the app bar.
However, clicking on either one of the menu items opens the sub-menus for profile-menu. So in other words clicking on simple-menu opens up Profile, My Account, and Logout but it should open up New, List, and Report.
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import IconButton from '#material-ui/core/IconButton';
import AccountCircle from '#material-ui/icons/AccountCircle';
import Button from '#material-ui/core/Button';
import Menu from '#material-ui/core/Menu';
import MenuItem from '#material-ui/core/MenuItem';
import MenuIcon from '#material-ui/icons/Menu';
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
},
menuButton: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
},
}));
function MenuAppBar() {
const classes = useStyles();
const [auth, setAuth] = React.useState(true);
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
function handleMenu(event) {
setAnchorEl(event.currentTarget);
}
function handleClose() {
setAnchorEl(null);
}
return (
<div className={classes.root}>
<AppBar color="default" position="static">
<Toolbar>
<Typography variant="h6" className={classes.title}>
App
</Typography>
{auth && (
<div>
<Button
aria-owns={anchorEl ? 'simple-menu' : undefined}
aria-haspopup="true"
onClick={handleMenu}
>
Open Menu
</Button>
<Menu id="simple-menu" anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleClose}>
<MenuItem onClick={handleClose}>New</MenuItem>
<MenuItem onClick={handleClose}>List</MenuItem>
<MenuItem onClick={handleClose}>Report</MenuItem>
</Menu>
</div>
)}
{auth && (
<div>
<IconButton
aria-owns={anchorEl ? 'profile-menu' : undefined}
aria-haspopup="true"
onClick={handleMenu}
>
<AccountCircle />
</IconButton>
<Menu id="profile-menu" anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleClose}>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</Menu>
</div>
)}
</Toolbar>
</AppBar>
</div>
);
}
export default MenuAppBar;
First you need to define the menus in an enum / object
const MenuTypes = Object.freeze({
Simple: 'simple',
Profile: 'profile'
})
Add another state item to track your active menu
const [activeMenu, setActiveMenu] = React.useState(null);
then update handleMenu to accept the menu type and set it in state.
I can't remember off the top of my head if the menuType will be the first or second argument so verify this.
function handleMenu(menuType, event) {
setActiveMenu(menuType);
setAnchorEl(event.currentTarget);
}
Then your click callbacks need to reflect the correct menu
<Button
aria-owns={anchorEl ? 'simple-menu' : undefined}
aria-haspopup="true"
onClick={handleMenu.bind(null, MenuTypes.Simple}
>
Then you need to reference that type to determine which is currently active
open={!!activeMenu && activeMenu === MenuTypes.Simple}
Dont forget to update the close handler as well
function handleClose() {
setActiveMenu(null);
setAnchorEl(null);
}
Let me know if anything here doesn't make sense and I'll try and explain in more detail what is confusing :)