Skip to content

Instantly share code, notes, and snippets.

@riskers
Last active August 12, 2023 20:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save riskers/c2e4dfdd4a16d501493f400317b3995f to your computer and use it in GitHub Desktop.
Save riskers/c2e4dfdd4a16d501493f400317b3995f to your computer and use it in GitHub Desktop.
make material-ui dialog component imperative by react context hook
import { useDialog } from "@/components/dialog/index";
import DialogTitle from "@material-ui/core/DialogTitle";
const App = () => {
const [openDialog, closeDialog] = useDialog();
return <button
href="#!"
onClick={() => {
openDialog({
children: (
<>
<DialogTitle>Payment failed</DialogTitle>
</>
),
});
}}
>
click
</button>
}
import React from "react";
import { Dialog } from "@material-ui/core";
type ProviderContext = readonly [(option: DialogOption) => void, () => void];
const EMPTY_FUNC = () => {};
const DialogContext = React.createContext<ProviderContext>([
EMPTY_FUNC,
EMPTY_FUNC,
]);
export const useDialog = () => React.useContext(DialogContext);
type DialogParams = {
children: React.ReactNode;
open: boolean;
onClose?: Function;
onExited?: Function;
};
type DialogOption = Omit<DialogParams, "open">;
type DialogContainerProps = DialogParams & {
onClose: () => void;
onKill: () => void;
};
function DialogContainer(props: DialogContainerProps) {
const { children, open, onClose, onKill } = props;
return (
<Dialog open={open} onClose={onClose} onExited={onKill}>
{children}
</Dialog>
);
}
export default function DialogProvider({ children }) {
const [dialogs, setDialogs] = React.useState<DialogParams[]>([]);
const createDialog = (option: DialogOption) => {
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 });
});
};
const contextValue = React.useRef([createDialog, closeDialog] as const);
return (
<DialogContext.Provider value={contextValue.current}>
{children}
{dialogs.map((dialog, i) => {
const { onClose, ...dialogParams } = dialog;
const handleKill = () => {
if (dialog.onExited) dialog.onExited();
setDialogs((dialogs) => dialogs.slice(0, dialogs.length - 1));
};
return (
<DialogContainer
key={i}
onClose={closeDialog}
onKill={handleKill}
{...dialogParams}
/>
);
})}
</DialogContext.Provider>
);
}
import React from "react";
import ReactDOM from "react-dom";
import DialogProvider from "@/components/dialog";
import App from "./App";
ReactDOM.render(
<DialogProvider>
<App />
</DialogProvider>,
document.getElementById("root")
);
@riskers
Copy link
Author

riskers commented Apr 22, 2022

以 MUI Snackbar 组件为例,说明 useContext + useReducer 可以替代 redux:

https://riskers.notion.site/useContext-useReducer-redux-155c13635f6344cc84eb34f9a1923e5c

@riskers
Copy link
Author

riskers commented Apr 22, 2022

题外话,MUI 文档说到这个组件可以直接使用 imperative 式的组件:

https://github.com/iamhosseindhv/notistack

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment