Material-UI - How to open Dialog imperatively/programmatically

喜夏-厌秋 提交于 2021-01-27 10:22:26

问题


Normally this is how you use Material-UI's Dialog. The code below is taken from Material-UI's docs

export default function AlertDialog() {
  const [open, setOpen] = React.useState(false);
  const handleClickOpen = () => setOpen(true);
  const handleClose = () => setOpen(false);

  return (
    <div>
      <Button variant="outlined" color="primary" onClick={handleClickOpen}>
        Open Dialog
      </Button>
      <Dialog open={open} onClose={handleClose}>
       {...}
      </Dialog>
    </div>
  );
}

But I want it to create the Dialog imperatively, sort of like fire and forget. I do not want to embed the Dialog component in other components whenever I need to create them. Ideally I'd want to call it like this

createDialog(<>
   <h1>My Dialog</h1>
   <span>My dialog content</span>
   <button onClick={() => closeDialog()}>Close</button>
</>)

So my component definition'd look like this

const createDialog = () => {
   // ???
}
const closeDialog = () => {
   // ???
}
export default function AlertDialog() {
  const [open, setOpen] = React.useState(false);
  const handleClickOpen = () => setOpen(true);
  const handleClose = () => {
     createDialog(<>
        <h1>My Dialog</h1>
        <span>My dialog content</span>
        <button onClick={() => closeDialog()}>Close</button>
     </>)
  };

  return (
    <Button variant="outlined" color="primary" onClick={handleClickOpen}>
      Open Dialog
    </Button>
  );
}

回答1:


You can reuse dialogs using React's Provider pattern. The official React document has explained in good detail so I won't cover it again here.

First create a custom Provider component in this case I'll call DialogProvider. This component will manage a list of Dialogs in local state.

const DialogContext = React.createContext();

export default function DialogProvider({ children }) {
  const [dialogs, setDialogs] = React.useState([]);

  return (
    <DialogContext.Provider {...}>
      {children}
    </DialogContext.Provider>
  );
}

As you can see, we have an array of dialogs here, it contains the dialog props that will be mapped to the actually <Dialog /> component when rendering.

export default function DialogProvider({ children }) {
  const [dialogs, setDialogs] = React.useState([]);

  return (
    <DialogContext.Provider {...}>
      {children}
      {dialogs.map((dialog, i) => {
        return <DialogContainer key={i} {...dialog} />;
      })}
    </DialogContext.Provider>
  );
}

The <DialogContainer/> is the parent component of the <Dialog/>. Put anything that you want to be reusable in there. Here is a minimum example to get you started.

function DialogContainer(props: DialogContainerProps) {
  const { children, open, onClose, onKill } = props;

  return (
    <Dialog open={open} onClose={onClose} onExited={onKill}>
      {children}
    </Dialog>
  );
}

We can create and remove the dialog using setState as normal.

const [dialogs, setDialogs] = React.useState([]);

const createDialog = (option) => {
  const dialog = { ...option, open: true };
  setDialogs((dialogs) => [...dialogs, dialog]);
};

const closeDialog = () => {
  setDialogs((dialogs) => {
    const latestDialog = dialogs.pop();
    if (!latestDialog) return dialogs;
    if (latestDialog.onClose) latestDialog.onClose();
    return [...dialogs].concat({ ...latestDialog, open: false });
  });
};

But how do we call them in other components when we defined them here? Well, remember we're using Provider component here, which means we can pass the context data down so other components can reference, in this case we want to pass the createDialog and closeDialog down.

const [dialogs, setDialogs] = React.useState([]);
const createDialog = (option) => {/*...*/};
const closeDialog = () => {/*...*/};
const contextValue = React.useRef([createDialog, closeDialog]);

return (
  <DialogContext.Provider value={contextValue.current}>
    {children}
    {dialogs.map((dialog, i) => ...)}
  </DialogContext.Provider>
);

We're almost done here, now we need to add the DialogProvider to the component tree.

export default function App() {
  return (
    <DialogProvider>
      <App {...} />
    </DialogProvider>
  );
}

But before we can use them, we should create a hook to easily access the context from the parent. So in your DialogProvider.jsx

export const useDialog = () => React.useContext(DialogContext);

Now we can use it like this.

import { useDialog } from "./DialogProvider";

export default function Content() {
  const [openDialog, closeDialog] = useDialog();
  const onOpenDialog = () => {
    openDialog({
      children: (
        <>
          <DialogTitle>This dialog is opened imperatively</DialogTitle>
          <DialogContent>Some content</DialogContent>
          <DialogActions>
            <Button color="primary" onClick={closeDialog}>Close</Button>
          </DialogActions>
        </>
      )
    });
  };

  return (
    <Button variant="contained" onClick={onOpenDialog}>
      Show dialog
    </Button>
  );
}

Live Demo

You can play around in the live demo here



来源:https://stackoverflow.com/questions/63737526/material-ui-how-to-open-dialog-imperatively-programmatically

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!