Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dialog does not reset internal state when closing. #16325

Closed
2 tasks done
matthewdordal opened this issue Jun 21, 2019 · 20 comments
Closed
2 tasks done

Dialog does not reset internal state when closing. #16325

matthewdordal opened this issue Jun 21, 2019 · 20 comments
Labels
component: modal This is the name of the generic UI component, not the React module! support: question Community support but can be turned into an improvement

Comments

@matthewdordal
Copy link
Contributor

  • This is not a v0.x issue.
  • I have searched the issues of this repository and believe that this is not a duplicate.

Expected Behavior 🤔

When I open a dialog and change the state of the DialogContent the state should be reset once the dialog is closed. That way previous state is not persisted when the dialog is opened again.

Current Behavior 😯

Open a dialog that can set state in it's content. Change the state and close the dialog. Open the dialog again and the previous state is displayed in the dialog content.

Steps to Reproduce 🕹

Here is a Code Sandbox demonstrating the issue.

I have also attached a gif (hopefully) better demonstrate the issue.

dialog

Context 🔦

I have dialogs with form inputs. When a user enters text into the input, closes the dialog, and then opens the dialog again the input shows the previously entered text instead of the default state values

Your Environment 🌎

Tech Version
Material-UI 4.0.1
React 16.8.6
Browser Chrome
TypeScript
etc.
@matthewdordal
Copy link
Contributor Author

Closing this issue. I apologize, it looks like I did not set the keepMounted prop correctly.

@tkforce
Copy link

tkforce commented Sep 10, 2019

@matthewdordal I'm running into similar issue but cannot reproduce with the sample code you provided, care to elaborate a bit more? Thanks!

@asalem1
Copy link

asalem1 commented Sep 12, 2019

@tkforce I'm strangely running into a similar issue where the initial state of a component for a specific array prop is never reset. What's the data type that isn't being reset for your component, and how are you setting the initial state?

@asalem1
Copy link

asalem1 commented Sep 16, 2019

My issue stemmed from the fact that I had been setting the initial state of my component as an object outside the component. This was causing the data to be stored regardless of the component unmounting. As a means of resolving this, I simply set the initialState as a property of the class using the get method

@DollarAkshay
Copy link

Closing this issue. I apologize, it looks like I did not set the keepMounted prop correctly.

Setting the keepMounted to true or false property did not clear the state.

@oliviertassinari oliviertassinari added component: modal This is the name of the generic UI component, not the React module! support: question Community support but can be turned into an improvement labels Mar 24, 2021
@oliviertassinari
Copy link
Member

@seansullivan
Copy link

I have been successful in using the following pattern when I need to wholesale reset the modal/dialog state. The crux of this approach is updating of the key value to get the state to renew.

const ComponentWithDialog: React.FC = () => {
  const openId = useRef<number>(1);

  const [open, setOpen] = useState<boolean>(false);

  useEffect(() => {
    if (!open) { // Increment id each time modal closes
      openId.current = openId.current + 1;
    }
  }, [open]);

  return (
      <>
        <Dialog key={openId.current} open={open}>Dialog Content with State</Dialog>
        <Button onClick={() => { setOpen(true) }}
      </>
  );
};

There may be a more elegant solution that I'm not aware of, but I typically find it most convenient to nuke and pave with each close.

@T-Nagui
Copy link

T-Nagui commented Oct 14, 2021

i found easy solution for same pb :
use your own state for open / close modal to mount and unmount :
{ (open) ? blah blah : null }

@malbaugh
Copy link

malbaugh commented Jun 13, 2022

As a workaround, I have been using Formik to handle the state of any forms contained within a Dialog. This eliminates the necessity of a key for the Dialog component in the parent component.

Example:

import {
  Box,
  Button,
  Dialog,
  DialogContent,
  IconButton,
  Typography,
} from "@mui/material";
import XIcon from "@mui/icons-material/Close";
import { QuillEditor } from "../quill-editor";
import { Formik } from "formik";

interface Props {
  note?: string;
  open: boolean;
  onClose: any;
}
export function CreateMaterialNoteDialog(props: Props) {
  const { note, open, onClose, ...other } = props;

  const handleClose = () => {
    onClose({ save: false, note: undefined });
  };

  const handleCancel = () => {
    onClose({ save: false, note: undefined });
  };

  return (
    <Dialog
      fullWidth
      maxWidth="sm"
      onClose={handleClose}
      open={open}
      {...other}
    >
      <DialogContent sx={{ p: 0 }}>
        <IconButton
          sx={{
            position: "absolute",
            top: 8,
            right: 8,
          }}
          onClick={() => handleCancel()}
        >
          <XIcon fontSize="small" />
        </IconButton>

        <Formik
          initialValues={{
            note: note,
          }}
          enableReinitialize={true}
          validateOnMount={true}
          validateOnChange={true}
          onSubmit={async (values, helpers) => {
            try {
              onClose({ save: true, note: values.note });
            } catch {
              onClose({ save: false, note: undefined });
            }
          }}
        >
          {({ values, handleSubmit, setFieldValue }) => (
            <form onSubmit={handleSubmit}>
              <Box
                sx={{
                  display: "flex",
                  pt: 2,
                  px: 3,
                }}
              >
                <Typography variant="h5">Record your notes</Typography>
              </Box>
              <Box sx={{ p: 3, width: "100%" }}>
                <QuillEditor
                  onChange={(value) => {
                    setFieldValue("note", value);
                  }}
                  placeholder="Notes on this material..."
                  sx={{ height: 400 }}
                  value={values.note}
                />
              </Box>
              <Box
                sx={{
                  display: "flex",
                  justifyContent: "flex-end",
                  px: 3,
                  pb: 3,
                }}
              >
                <Button
                  sx={{ width: "100%" }}
                  variant="contained"
                  type="submit"
                >
                  Record Note
                </Button>
              </Box>
            </form>
          )}
        </Formik>
      </DialogContent>
    </Dialog>
  );
}

@abhishekn123
Copy link

abhishekn123 commented Jul 10, 2022

@seansullivan @oliviertassinari @matthewdordal @DollarAkshay @tkforce

 useEffect(()=>{
    if(!open){
!reset your component states here}
 },[open])

@DwayneGit
Copy link

DwayneGit commented Jul 24, 2022

I found that what i was doing wrong was putting the dialog with in its own component rather than defining with in the parent and defining the content of the dialog with in its own component.
ex)

const Parent(){
    const [open, setOpen] = useState(false);
    return (<> 
        <Dialog open={open}>
             <MyDialogContent />
        </Dialog>
    </>)
}


const MyDialogContent(){
    const [stuff, setStuff] = useState(false); // will be reset every time
    return (
        <>
            <DialogTitle>Stuff</DialogTitle>
            {...}
            <DialogActions> </DialogActions>
        </>
    )
}

when you think about it every thing out side of {...} does not depend on the open state and will persist regardless of a open being passed in.
ex)

const Parent(){
    const [open, setOpen] = useState(false);
    return (<MyDialog open={open} />)
}

const MyDialog({ open }){
    const [stuff, setStuff] = useState(false); // does not depend on if dialog is open or not will not reset
    return (
        <Dialog open={open}>
            <DialogTitle>Stuff</DialogTitle>
            {...}
            <DialogActions> </DialogActions>
        </Dialog>
    )
}

@TamirCode
Copy link

TamirCode commented Aug 15, 2022

when you think about it every thing out side of {...} does not depend on the open state and will persist regardless of a open being passed in. ex)

the reasoning does not make sense but it works regardless, thanks

@TamirCode TamirCode mentioned this issue Aug 15, 2022
2 tasks
@DwayneGit
Copy link

when you think about it every thing out side of {...} does not depend on the open state and will persist regardless of a open being passed in. ex)

the reasoning does not make sense but it works regardless, thanks

It does if you look at the second example. stuff and setstuff are not with in the dialog so it will not be affected/reset when the dialog is opened or closed. But I could have said that more clearly

@TamirCode
Copy link

It does if you look at the second example. stuff and setstuff are not with in the dialog so it will not be affected/reset when the dialog is opened or closed. But I could have said that more clearly

hmm I see what you mean now

@jorgegarba
Copy link

I have been successful in using the following pattern when I need to wholesale reset the modal/dialog state. The crux of this approach is updating of the key value to get the state to renew.

const ComponentWithDialog: React.FC = () => {
  const openId = useRef<number>(1);

  const [open, setOpen] = useState<boolean>(false);

  useEffect(() => {
    if (!open) { // Increment id each time modal closes
      openId.current = openId.current + 1;
    }
  }, [open]);

  return (
      <>
        <Dialog key={openId.current} open={open}>Dialog Content with State</Dialog>
        <Button onClick={() => { setOpen(true) }}
      </>
  );
};

There may be a more elegant solution that I'm not aware of, but I typically find it most convenient to nuke and pave with each close.

I'm aware of the virtual DOM that React handles, but not in this way, I mean, if an element stores the same key, it could preserve the last state, really nice trick

@david-ragazzi
Copy link

I have been successful in using the following pattern when I need to wholesale reset the modal/dialog state. The crux of this approach is updating of the key value to get the state to renew.

const ComponentWithDialog: React.FC = () => {
  const openId = useRef<number>(1);

  const [open, setOpen] = useState<boolean>(false);

  useEffect(() => {
    if (!open) { // Increment id each time modal closes
      openId.current = openId.current + 1;
    }
  }, [open]);

  return (
      <>
        <Dialog key={openId.current} open={open}>Dialog Content with State</Dialog>
        <Button onClick={() => { setOpen(true) }}
      </>
  );
};

There may be a more elegant solution that I'm not aware of, but I typically find it most convenient to nuke and pave with each close.

This works to me! Thanks!

@Lioralon5
Copy link

import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";

function useDialogState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>, number] {
  const key = useRef<number>(0);

  const [open, setOpen] = useState(initialState);

  useEffect(() => {
    if (!open) key.current = key.current + 1;
  }, [open]);

  return [open, setOpen, key.current];
}

export default useDialogState;

Custom hook using @seansullivan approach.

@Mizan-Rifat
Copy link

This issue still exists. And I don't understand the purpose of keeping mounted the dialog? If I want to keep mounted the dialog then I can use the keepMounted prop. I think this is a bug and need to fix

@youngmoon715
Copy link

youngmoon715 commented May 22, 2024

@Mizan-Rifat

This issue still exists. And I don't understand the purpose of keeping mounted the dialog? If I want to keep mounted the dialog then I can use the keepMounted prop. I think this is a bug and need to fix

Either one of this will do

 <Dialog key={someUniqueKey} /> 

or simply

{open && <Dialog open /> } }

@DerekChristy
Copy link

DerekChristy commented Jul 23, 2024

This same behaviour is also present in angular material.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: modal This is the name of the generic UI component, not the React module! support: question Community support but can be turned into an improvement
Projects
None yet
Development

No branches or pull requests