I am new here and I saw some related answer but it will use another package and class components, i have functional component and use MUI Dialog for popup but the functionality is only for one modal but I have many modal my code
import React from 'react'
import Image from '../../assets/images/banner1.png'
import Dialog from '#mui/material/Dialog';
import DialogActions from '#mui/material/DialogActions';
import DialogContent from '#mui/material/DialogContent';
import DialogContentText from '#mui/material/DialogContentText';
import DialogTitle from '#mui/material/DialogTitle';
import Button from '#mui/material/Button';
export default function Popups() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div className='image-data-grids'>
<div className='image-and-data-section'>
<img onClick={handleClickOpen} className='image-data-grid-image' src={Image} alt='Img' />
</div>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Content1
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Disagree</Button>
</DialogActions>
</Dialog>
<div className='image-and-data-section'>
<img onClick={handleClickOpen} className='image-data-grid-image' src={Image} alt='Img' />
</div>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogContent>
<DialogContentText id="alert-dialog-description">
content2
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Disagree</Button>
</DialogActions>
</Dialog>
</div>
)
}
here handleClickOpen and handleClose are the function for open and close for single modal, i have multiple modals. how customize these two function for multiple modal, i am new in react please help me to solve this issue, Thanks in advance
I know you are talking about having multiple state dependencies within a Component. I think you can do this
function StackOverflow() {
// We create a state dep that holds all our modals open/close state
const [myModals, setMyModals] = useState({
modalA: true,
modalB: false,
})
// This will return an api in which we can toggle, close or open a modal
// ie: set the boolean to true or false
const getModalHanlder = (modalName) => {
return {
isOpen: myModals[modalName],
open: () => setMyModals((state) => ({ ...state, [modalName]: true })),
close: () => setMyModals((state) => ({ ...state, [modalName]: false })),
toggle: () =>
setMyModals((state) => ({ ...state, modalA: !state[modalName] })),
}
}
const modalA = getModalHanlder("modalA")
// Here we invoke our function and pass on the desired modal prop name from
// which we desire to create an api
// We can then create this for another modal, modalB
const modalB = getModalHanlder("modalB")
return (
<div>
<b>MODAL_A: {`${modalA.isOpen}`}</b>
<br />
<button onClick={modalA.toggle}>TOGGLE</button>
<button onClick={modalA.close}>CLOSE</button>
<button onClick={modalA.open}>OPEN</button>
</div>
)
}
This is one approach another one can be to create an state for each modal, like
const [isOpenModalA, setIsOpenModalA] = useState(false)
// And this for each modal state
A fancy one can be to use a hook as useReducer and update each modal state that depends on actions you dispatch, https://reactjs.org/docs/hooks-reference.html#usereducer
Related
I have a React component that gets data from a parent page/component. I use this data object (jobData) to populate a hook variable value when the component (a modal) is fired. The jobData looks like this: [{id: '17003', file_name: 'My_File', type: 'Medium', state: 'Arkansas'}]. In the browser debugger, I can see the jobData getting passed into the component, but when it gets to return ....<TextField .... value={productID} the productID says undefined! Any suggestions as to what I am doing wrong? I want the TextField to display the value of jobData[0]['id'] when it fires and then store the value of productID when it cahnges.
import React, { useState, useEffect } from 'react';
import { Button, TextField, Dialog, DialogActions, DialogContent, DialogTitle, Modal,
FormControl, Select, InputLabel } from '#mui/material';
import { createTheme, ThemeProvider } from '#mui/material/styles';
export default function ScheduleProdsModal({jobData}) {
const [open, setOpen] = useState(false);
const handleClose = () => setOpen(false);
const handleOpen = () => setOpen(true);
// Below is the hook with problems
let [productID, setProductID] = useState(jobData[0]["id"]);
return (
<div>
<ThemeProvider theme={theme}>
<Button color="neutral" variant="contained" cursor="pointer" onClick={handleOpen}>Schedule Products</Button>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>Schedule Products</DialogTitle>
<DialogContent >
<FormControl fullWidth style={{marginTop: '5px', marginBottom: '5px'}}>
<TextField
autoFocus
margin="dense"
width="100%"
id="my_id"
label="My ID"
type="text"
value={productID}
variant="outlined"
onChange={(e) => {
setProductID(e.target.value);
}}
/>
</FormControl>
</DialogContent>
<DialogActions style={{marginRight: 'inherit'}}>
<Button color="neutral" variant="contained" cursor="pointer" onClick={handleClose}>Close</Button>
<Button color="neutral" variant="contained" cursor="pointer" onClick={handleScheduler}>Schedule</Button>
</DialogActions>
</Dialog>
</ThemeProvider>
</div>
);
}
It seems like you have set jobData array when initialising the projectId, which will be undefined in the first load unless it has been handled in the parent component. Therefore as a workaround we normally use useEffect hook to get the upcoming props changes. So, when the jobData array is not null, you can use setProductID to get the relevant product id inside the useEffect hook. For the time period that you are not getting the data, you can use your favourite loading mechanism to show a loading screen. Find the below code snippet.
const [open, setOpen] = useState(false);
const handleClose = () => setOpen(false);
const handleOpen = () => setOpen(true);
// Below is the hook with problems
let [productID, setProductID] = useState(0);
useEffect(() => {
if(jobData && jobData.length > 0){
setProductID(Number(jobData[0]["id"]))
}
}, [jobData])
// You can put more conditions like undefined and null checking if you want
if(productID === 0){
// Use your fav loading page or loader here
return <>Loading...</>
}
return (
<div>
<ThemeProvider theme={theme}>
Hope this would help.
I'm using modals from the react-material-ui library for a project, and I noticed a side effect when trying to open/close the dialog component. Try this code (code sandbox):
import { useState } from "react";
import moment from "moment";
import Button from "#material-ui/core/Button";
import Dialog from "#material-ui/core/Dialog";
import DialogTitle from "#material-ui/core/DialogTitle";
import DialogActions from "#material-ui/core/DialogActions";
import "./styles.css";
export default function App() {
const [open, setOpen] = useState(false);
const timeStamp = moment().format("HH:mm:ss SS");
const handleOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div className="App">
<h1>Mui Dialog</h1>
<h2>Rendered on {timeStamp}</h2>
<Button variant="outlined" color="primary" onClick={handleOpen}>
Open simple dialog
</Button>
<Dialog open={open}>
<DialogTitle>This is a simple dialog</DialogTitle>
<DialogActions>
<Button onClick={handleClose} color="primary" autoFocus>
Close
</Button>
</DialogActions>
</Dialog>
</div>
);
}
You'll see that clicking on the button open will cause a re-render and closing the dialog will have the same effect, it's normal because state changed after calling hooks. This is often undesirable, I don't want to re-render the page when opening or after closing.
So I tried to use the following solution, based of refs (code sandbox):
import { useState, useRef, forwardRef, useImperativeHandle } from "react";
import moment from "moment";
import Button from "#material-ui/core/Button";
import Dialog from "#material-ui/core/Dialog";
import DialogTitle from "#material-ui/core/DialogTitle";
import DialogActions from "#material-ui/core/DialogActions";
import "./styles.css";
const SimpleDialog = forwardRef(({ title }, ref) => {
const [open, setOpen] = useState(false);
const innerRef = useRef();
const handleClose = () => {
setOpen(false);
};
useImperativeHandle(ref, () => ({
openDialog: () => setOpen(true),
closeDialog: () => setOpen(false)
}));
return (
<Dialog open={open} ref={innerRef}>
<DialogTitle>{title}</DialogTitle>
<DialogActions>
<Button onClick={handleClose} color="primary" autoFocus>
Close
</Button>
</DialogActions>
</Dialog>
);
});
export default function App() {
const timeStamp = moment().format("HH:mm:ss SS");
const dialogRef = useRef();
const handleOpen = () => {
dialogRef.current.openDialog();
};
return (
<div className="App">
<h1>Mui Dialog</h1>
<h2>Rendered on {timeStamp}</h2>
<Button variant="outlined" color="primary" onClick={handleOpen}>
Open simple dialog
</Button>
<SimpleDialog ref={dialogRef} title="This is a simple dialog" />
</div>
);
}
My question is: is this a correct approach to solve the problem of unwanted re-renders in the case of react-material-ui modals ?
Regards.
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'm trying to make a reusable confirmation modal with Material UI but when I press CANCEL or OK the modal does not close.
The code is here:
https://codesandbox.io/s/lucid-hoover-sput6?file=/src/App.js
I can't figure it out why the pop don't dissapear.
LE: added the code here so it remains
ConfirmModal.js
import React from "react";
import { Button } from "#material-ui/core";
import Dialog from "#material-ui/core/Dialog";
import DialogActions from "#material-ui/core/DialogActions";
import DialogContent from "#material-ui/core/DialogContent";
import DialogContentText from "#material-ui/core/DialogContentText";
const ConfirmModal = (props) => {
const { content, open, setOpen, onConfirm } = props;
return (
<Dialog
open={open}
onClose={() => setOpen(false)}
aria-labelledby="dialog-title"
>
<DialogContent>
<DialogContentText>{content}</DialogContentText>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={() => setOpen(false)} color="primary">
Cancel
</Button>
<Button
onClick={() => {
setOpen(false);
onConfirm();
}}
color="primary"
>
OK
</Button>
</DialogActions>
</Dialog>
// </div>
);
};
export default ConfirmModal;
App.js
import React, { useState } from "react";
import { IconButton } from "#material-ui/core";
import { Input as InputIcon } from "#material-ui/icons";
import ConfirmModal from "./ConfirmModal";
export default function App() {
const [confirmOpen, setConfirmOpen] = useState(false);
const handleLogout = () => {
console.log("this hould logout the user");
};
return (
<div className="App">
<h2>Press the button below so the confirmation modal appears </h2>
<IconButton color="inherit" onClick={() => setConfirmOpen(true)}>
<InputIcon />
<ConfirmModal
content="Are you sure you want to leeeave us ?"
open={confirmOpen}
setOpen={setConfirmOpen}
onConfirm={handleLogout}
/>
</IconButton>
</div>
);
}
Move the modal out of the button. The Modal's cancel/confirm/backdrop click events are propagating (bubbling) up to the open button (IconButton) and its onClick handler is just reopening the modal by setting confirmOpen state true.
export default function App() {
const [confirmOpen, setConfirmOpen] = useState(false);
const handleLogout = () => {
console.log("this hould logout the user");
};
return (
<div className="App">
<h2>Press the button below so the confirmation modal appears </h2>
<IconButton color="inherit" onClick={() => setConfirmOpen(true)}>
<InputIcon />
</IconButton>
<ConfirmModal
content="Are you sure you want to leeeave us ?"
open={confirmOpen}
setOpen={setConfirmOpen}
onConfirm={handleLogout}
/>
</div>
);
}
I'm trying to turn Material UI's dialog into a "useDialog" hook so it keeps track of it's own open state.
Unfortunately I've encountered a problem that whenever I update a state further up the hierarchy, the dialog flickers and I'm not exactly sure why and how to circumvent it. I feel like a useRef is needed there somewhere, but I'm not sure. Here's a reproduced minimal example: https://codesandbox.io/s/flickering-dialog-minimal-example-ehruf?file=/src/App.js
And the code in question:
import React, { useState } from "react";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle
} from "#material-ui/core";
export default function App() {
const [openDialog, Dialog] = useDialog();
const [counter, setCounter] = useState(0);
return (
<div>
<Dialog title="Hello">
<div>{counter}</div>
<button onClick={() => setCounter(counter => counter + 1)}>
Increase
</button>
</Dialog>
<button onClick={openDialog}>Open dialog</button>
</div>
);
}
const useDialog = () => {
const [open, setOpen] = useState(false);
const handleClose = () => {
setOpen(false);
};
const someDialog = ({ title, children }) => {
return (
<Dialog open={open} onClose={handleClose}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>{children}</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
);
};
return [
() => {
setOpen(true);
},
someDialog
];
};
The reason the dialog flickers is that a new Dialog component is created on every render(as a result of state change) in App. The old Dialog is unmounted and replaced by the new Dialog.
A rule of thumb is you should never define components while rendering.
That's why I suggest you separate your custom dialog component from useDialog hook:
const MyDialog = ({ open, handleClose, title, children }) => {
return (
<Dialog open={open} onClose={handleClose}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>{children}</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
);
};
You can, however, keep some of the logic inside useDialog and reuse them:
const useDialog = () => {
const [open, setOpen] = useState(false);
const openDialog = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const props = {
open,
handleClose
};
return [openDialog, props];
};
More about why returning components from hook can be a bad idea.
Custom hooks are not made for returning a component, instead they are used to create a common logic which will be shared by different components.
In your case I would suggest you to create a common component for your dialog. And use this component wherever you want. Like this:
<CustomDialog open={open}>
// Your jsx here
</CustomDialog>
const CustomDialog = ({children}) => {
return <Dialog open={open} onClose={handleClose}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>{children}</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
}
For more information about custom hooks:
https://reactjs.org/docs/hooks-custom.html
To whomever finds this issue because of my mistake. I was using a styled Dialog and defined the styled Dialog inside the functional component. Just put the custom styling outside of any component.
// This needed to be outstide of the BetterDialog component
const CustomDialog = styled(Dialog)({
"& .MuiDialog-paper": {
zIndex: 90
}
})
...
function BetterDialog(props: BetterDialogProps) {
...