Material-UI - How to open Dialog imperatively/programmatically

Nea*_*arl 8 javascript reactjs material-ui react-material

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>
  );
}
Run Code Online (Sandbox Code Playgroud)

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>
</>)
Run Code Online (Sandbox Code Playgroud)

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>
  );
}
Run Code Online (Sandbox Code Playgroud)

Nea*_*arl 19

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>
  );
}
Run Code Online (Sandbox Code Playgroud)

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>
  );
}
Run Code Online (Sandbox Code Playgroud)

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>
  );
}
Run Code Online (Sandbox Code Playgroud)

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 });
  });
};
Run Code Online (Sandbox Code Playgroud)

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>
);
Run Code Online (Sandbox Code Playgroud)

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

export default function App() {
  return (
    <DialogProvider>
      <App {...} />
    </DialogProvider>
  );
}
Run Code Online (Sandbox Code Playgroud)

但是在我们可以使用它们之前,我们应该创建一个钩子来轻松地从父级访问上下文。所以在你的DialogProvider.jsx

export const useDialog = () => React.useContext(DialogContext);
Run Code Online (Sandbox Code Playgroud)

现在我们可以像这样使用它。

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>
  );
}
Run Code Online (Sandbox Code Playgroud)

现场演示

你可以在这里玩现场演示

编辑 DialogProvider