Avoid re-render while open/close material-ui Dialog component - reactjs

I've got some problem with omit re-render while click button which open Dialog component. Currently I use useState flag which toggle my Dialog component but I must avoid re-rendering because there's expensive operations on big data.
Anyone know approach how to Open Dialog by button Open and Close Dialog by click on Dialog's button Exit.
const Item = () => {
const popupRef = useRef()
const dialog = () => {
return(
<Dialog ref={popupRef}
keepMounted
fullWidth
maxWidth="md"
aria-labelledby="alert-dialog-slide-title"
aria-describedby="alert-dialog-slide-description">
<DialogTitle id="alert-dialog-slide-title">
<WrapperDialogHeader>Filters</WrapperDialogHeader>
<WrapperDialogCloseBtn>
<IconButton aria-label="close" >
<CloseIcon />
</IconButton>
</WrapperDialogCloseBtn>
</DialogTitle>
<DialogContent>
Something
</DialogContent>
<DialogActions>
<Button variant="outlined"
// onClick={ What handler here? }
color="primary">
<WrapperDialogOptionBtn>Exit</WrapperDialogOptionBtn>
</Button>
</DialogActions>
</Dialog>
)
}
return(
<>
<IconButton onClick={ /* What method here */ }>
<Typography variant="body1" color="primary">
<FilterListIcon fontSize="small" color="primary"/><WrapperFontSpecialSmall>Filters</WrapperFontSpecialSmall>
</Typography>
</IconButton>
{ dialog() }
</>
Methods like popupRef.current.click() not exist in ES6 I suppose.
How to build mechanism which toggle dialog without re-render whole component Item.

Move the dialog to its own component and wrap in memo()
const Dialog = ({ open, onClose }) => {
return (
<Dialog
keepMounted // why? you said the content is huge, so why keep it mounted
fullWidth
maxWidth="md"
aria-labelledby="alert-dialog-slide-title"
aria-describedby="alert-dialog-slide-description"
open={open}
onClose={onClose}
>
<DialogTitle id="alert-dialog-slide-title">
<WrapperDialogHeader>Filters</WrapperDialogHeader>
<WrapperDialogCloseBtn>
<IconButton aria-label="close" >
<CloseIcon />
</IconButton>
</WrapperDialogCloseBtn>
</DialogTitle>
<DialogContent>
Something
</DialogContent>
<DialogActions>
<Button variant="outlined" onClick={onClose} color="primary">
<WrapperDialogOptionBtn>Exit</WrapperDialogOptionBtn>
</Button>
</DialogActions>
</Dialog>
)
};
export default memo(Dialog); // IMPORTANT
In the Item component,
const Item = () => {
const [dialogOpen, setDialogOpen] = useState(false);
const handleDialogOpen = () => setDialogOpen(true);
// prevent function being recreated on state change
const handleDialogClose = useCallback(() => setDialogOpen(false), []);
return (
<>
<IconButton onClick={handleDialogOpen}>
<Typography variant="body1" color="primary">
<FilterListIcon fontSize="small" color="primary" /><WrapperFontSpecialSmall>Filters</WrapperFontSpecialSmall>
</Typography>
</IconButton>
<Dialog open={dialogOpen} onClose={handleDialogClose} />
</>
);
}
Sidenote, it appears the dialog contains some sort of filter UI, presumably one or more lists. You may want to have a look at react-window if you are going to create long lists.

Related

How can I fix the following warning: "Can't perform a React state update on an unmounted component"

Edit: I have been trying to implement a similar fix as this in my code, but I am very confused about how this method would translate in my code. I have been trying to apply that fix to my updateDose function, but it isn't working.
I am creating an app using React, Material UI, React Hook Form, and Yup.
I have two dialogs, one for the "edit dose" button and the "delete med" button for each card. On the "edit dose" dialog, the user inputs a new dose into a form.
I am getting the following warning if I try to update a medication's dose more than once (the first update shows no error, and then the second shows this)...
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I have searched other tutorials to try to find a fix based on other examples, but I am not having any luck. Thank you so much in advance and here's my relevant code...
const Medication = ({medication}) => {
const {handleSubmit, control, formState} = useForm({
mode: "onChange",
resolver: yupResolver(validationSchema)
});
// This handles the update medication dialog
const [openUpdate, setOpenUpdate] = useState(false);
const handleClickOpenUpdate = () => {
setOpenUpdate(true);
};
const handleCloseUpdate = () => {
setOpenUpdate(false);
};
// Function for the update dose button
function updateDose(medicationId, parsedMedications, data) {
let medication;
let index;
for (let i = 0; i < parsedMedications.length; i++) {
if (parsedMedications[i].id === medicationId) {
medication = parsedMedications[i];
index = i;
}
}
medication.dose = data.dose;
parsedMedications[index] = medication;
localStorage.setItem("medications", JSON.stringify(parsedMedications));
// This forces the dialog to close
setOpenUpdate(false);
}
return (
<Box>
<Card sx={cardSx}>
<CardContent>
<Typography sx={typographyMedicationSx} variant="h5">
Medication: {medication.medication}
</Typography>
<Typography sx={typographyMedicationSx} variant="h5">
Dose: {medication.dose} mg
</Typography>
</CardContent>
<Box>
<Button onClick={() => handleClickOpenUpdate()} size="large"
sx={buttonSx}
variant="contained">Edit
Dose</Button>
<Button onClick={() => handleClickOpen()} color="error"
size="large"
sx={buttonSx} variant="contained">Delete
Med </Button>
</Box>
</Card>
{/* Update medication dialog */}
<Dialog
open={openUpdate}
onClose={handleCloseUpdate}
TransitionComponent={Transition}
>
<DialogTitle sx={dialogTitleSx}>
{handleCloseUpdate ? (
<IconButton
aria-label="close"
onClick={handleCloseUpdate}
sx={iconButtonSx}
>
<CloseIcon/>
</IconButton>
) : null}
</DialogTitle>
<form
onSubmit={handleSubmit((data) => updateDose(medication.id, parsed, data))}
noValidate>
<Typography sx={updateDoseTypographySx} variant="h4">
Update dose
</Typography>
<Box
sx={boxSx}
>
<Controller
name="dose"
control={control}
defaultValue={""}
render={({field: {ref, ...field}, fieldState: {error}}) => (
<Autocomplete
{...field}
autoHighlight
disableClearable
isOptionEqualToValue={(option, value) => option.id === value.id}
id="dose-autocomplete"
onChange={(event, value) => field.onChange(value.label)}
options={doseSuggestions}
renderInput={(params) => (
<TextField
required
error={!!error}
helperText={error?.message}
id="dose"
label="Dose"
name="dose"
type="numeric"
inputRef={ref}
{...params}
/>
)}
/>
)}
/>
<Button disabled={!formState.isValid} size="large"
sx={formButtonSx} type="submit"
variant="contained">Submit</Button>
</Box>
</form>
</Dialog>
</Box>
)
};
const medications = parsed.map((medication, index) => {
return (<Medication medication={medication} key={"medication" + index}/>)
});
Someone on a slack community helped me figure it out! I needed to add this to the dialog... keepMounted={true}

Open a modal (or dialogue) from a dropdown asking the user if they would like to take an action

I would like to open a modal (or dialogue) when a user selects an option from a dropdown menu.
Eventually there will be a few options in the dropdown, and different dialogues/modals will be called and rendered depending on which dropdown option has been clicked. For now - how do I get the modal/dialogue to open with dropdown menu events?
I'm currently using the handleClose handler to attempt to open a dialogue since that event should be easy to grab and use right out of the docs for MUI Menu and MenuItem.
The origination call to the DropdownMenu component (which works well and shows the dropdown menu) occurs in a table through the click of an icon
<DropdownMenu options={['Show modal or dialogue to the user']}>
<MoreHorizIcon />
</DropdownMenu>
The DropdownMenu component (also working well itself, except for not triggering the dialogue/modal) looks like this
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 showModal = () => {
return (
<AlertDialog />
)
}
const handleClose = () => {
//the native alert dialogue works well
alert('I want to show a dialog or modal the same way this alert shows up and ask the user if they are sure they want to delete something')
//why isn't the custom alert dialog being called?
showModal();
setAnchorEl(null);
};
return (
<div>
<IconButton
aria-label="more"
id="more-button"
aria-controls="long-menu"
aria-expanded={open ? 'true' : undefined}
aria-haspopup="true"
onClick={handleClick}
>
{children}
</IconButton>
<Menu
id="dropdownmenu"
MenuListProps={{
'aria-labelledby': 'more-button'
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
PaperProps={{
style: {
maxHeight: ITEM_HEIGHT * 4.5,
width: '20ch'
}
}}
>
{options.map(option => (
<MenuItem key={option} onClick={handleClose} >
{option}
</MenuItem>
))}
</Menu>
</div>
);
};
export default DropdownMenu;
And the example starter dialogue I am using to get the ball rolling looks like this
const AlertDialog = () => {
const [open, setOpen] = React.useState(true);
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"Sweet Filler Dialog"}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Are you sure?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>NO</Button>
<Button onClick={handleClose} autoFocus>
YES
</Button>
</DialogActions>
</Dialog>
</div>
);
}
You can use a state variable to trigger the modal in the DropdownMenu component, whenever you wanted to show the dialog/modal.
const [isModalOpen, setIsModalOpen] = React.useState(false);
and then in the handleClose, you can update the modal state to open the modal.
const handleClose = () => {
setIsModalOpen(true)
setAnchorEl(null);
};
Then somewhere in your JSX of DropdownMenu, you can conditionally render the AlertDialog component like this
{isModalOpen ? <AlertDialog open={isModalOpen} setOpen={setIsModalOpen} /> : null}
Finally, update your AlertDialog component to use props to handle the closing of the modal.
const AlertDialog = ({ open, setOpen }) => {
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"Sweet Filler Dialog"}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Are you sure?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>NO</Button>
<Button onClick={handleClose} autoFocus>
YES
</Button>
</DialogActions>
</Dialog>
</div>
);
}

My handler does not set the state - Material UI

I am using the component "Dialog" from Material UI.
When I click on the button to close the Dialog (which triggers handleCLose), it does not set the state to false.
I read a similar problem here: OnClick Listeners not working after closing full-screen dialog using react-material-ui
but I have not managed to save it so far.
const Transition = React.forwardRef(function Transition(props, ref) {
return <Slide direction="up" ref={ref} {...props} />;
});
function Information() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
console.log(open)
};
return (
<div onClick={()=>handleClickOpen()}>
Information
<Dialog
open={open}
TransitionComponent={Transition}
keepMounted
onClose={handleClose}
aria-labelledby="alert-dialog-slide-title"
aria-describedby="alert-dialog-slide-description"
>
<DialogTitle id="alert-dialog-slide-title">{"Information"}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-slide-description">
Blablabla
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary"> // The function is here
Close
</Button>
</DialogActions>
</Dialog>
</div>
);
}
export default Information
It looks like you have wrapped your dialog in a div with an onClick function that sets the dialog to open. What is happening is that onClick is being called whenever you click inside the dialog. So even when you click the close button it is calling handleClickOpen. Try separating them:
return (
<> // Add wrapping fragment
<div onClick={()=>handleClickOpen()}>
Information
</div> // close div here
<Dialog
open={open}
TransitionComponent={Transition}
keepMounted
onClose={handleClose}
aria-labelledby="alert-dialog-slide-title"
aria-describedby="alert-dialog-slide-description"
>
<DialogTitle id="alert-dialog-slide-title">{"Information"}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-slide-description">
Blablabla
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
</>
);
}

Trying to use react material-ui checkbox inside Dialog

I'm trying to use material-ui checkbox inside dialog but onChange function seems not working. Please refer the following info for my code:
<Dialog open={showAssetListDialog}>
<DialogTitle id="responsive-dialog-title">
<h3 className="formHeader">Asset List</h3>
<hr />
</DialogTitle>
<DialogContent>
<Typography>
{ availableAssets && Object.keys(assetList).map((key, index) => (
<p>
<Checkbox
const asset_id = {assetList[index].value}
checked = {assetList[index].asset_id}
color = "primary"
onChange = {props.handleCheckboxChange('asset_id')}
onCheck = {props.handleCheckboxChange}
>
</Checkbox>
{assetList[index].label}
</p>
))}
</Typography>
</DialogContent>
<DialogActions>
<Button
color="success"
className="btnSave"
onClick={props.handleAssetListClose}
>
Done
</Button>
</DialogActions>
</Dialog>

Refactoring react code

I have a class component that is a header-bar and when you click on an icon a dialog-box pop up where you can input data and save it. I am trying to split these two items- header-bar and dialog-box into two different components. Is the best way to do this to redirect the from the header-bar component to a new dialog-box component. At the moment i have both of them in one component.Thanks for the advice.
const{classes} = this.props
return (
<div>
<Dialog
open={this.state.open}
onClose={this.handleClose}
aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Add Folder</DialogTitle>
<DialogContent>
<TextField
margin="normal"
onChange={this.handleChange('name')}
/>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color="primary">
Cancel
</Button>
<Button onClick={this.handleClose} color="primary"
onChange={this.handleFieldChange}
value={this.state.value}>
Create
</Button>
</DialogActions>
</Dialog>
<AppBar position="static" className={classes.bar} >
<Toolbar>
<IconButton color="inherit" aria-label="createfolder">
<SvgIcon>
<path d={createfolder}
onClick={this.handleClickOpen}
name="firstName"/>
</SvgIcon>
</IconButton>
</Toolbar>
</AppBar>
</div>
);
No, the best way to do this is not to redirect but same way, in a more modular or component/react way. You can create a functional component of dialog box and include it in your main component,
In the below code the functional component has been made inside the main component class itself so it can access the this.
DialogBox = ({state}) =>(
<Dialog
open={state.open}
onClose={this.handleClose}
aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Add Folder</DialogTitle>
<DialogContent>
<TextField
margin="normal"
onChange={this.handleChange('name')}
/>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color="primary">
Cancel
</Button>
<Button onClick={this.handleClose} color="primary"
onChange={this.handleFieldChange}
value={state.value}>
Create
</Button>
</DialogActions>
</Dialog>
)
Now include this in your header-bar component,
const{classes} = this.props
return (
<div>
<AppBar position="static" className={classes.bar} >
<Toolbar>
<IconButton color="inherit" aria-label="createfolder">
<SvgIcon>
<path d={createfolder}
onClick={this.handleClickOpen}
name="firstName"/>
</SvgIcon>
</IconButton>
</Toolbar>
</AppBar>
<this.DialogBox state={this.state}/>
</div>
);

Resources