Skip to content

Commit

Permalink
feat: add a modal confirm dialog hook
Browse files Browse the repository at this point in the history
  • Loading branch information
tomcur committed Jan 18, 2024
1 parent b2a7cd2 commit 6974f32
Showing 1 changed file with 94 additions and 0 deletions.
94 changes: 94 additions & 0 deletions astroplant-frontend/src/Components/ModalDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import React, {
PropsWithChildren,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";

import style from "./ModalDialog.module.css";
Expand Down Expand Up @@ -123,3 +125,95 @@ export function ModalConfirmDialog({
</ModalDialog>
);
}

export type UseModalConfirmDialogValue = {
element: React.ReactNode;
confirmed: boolean;
trigger: ({
header,
body,
}: {
header?: string;
body?: React.ReactNode;
}) => Promise<boolean>;
isOpen: boolean;
};

/**
* A hook to imperatively trigger a modal confirm dialog.
*
* Example:
*
* ```tsx
* const { element, trigger } = useModalConfirmDialog();
* const someCallback = (async () => {
* const confirmed = await trigger({header: "Are you sure?", body: <>Please confirm.</>});
* if (confirmed) {
* // do something
* }
* });
* return <>{element}{otherChildren}</>
* ```
*/
export function useModalConfirmDialog(): UseModalConfirmDialogValue {
const [confirmed, setConfirmed] = useState(false);
const [open, setOpen] = useState(false);
const [resolve, setResolve] = useState<
((value: boolean | PromiseLike<boolean>) => void) | null
>(null);

const [content, setContent] = useState<{
header?: string;
body?: React.ReactNode;
}>({});

const trigger = useMemo(() => {
return (content: { header?: string; body?: React.ReactNode }) => {
setConfirmed(false);
setOpen(true);
const p = new Promise<boolean>((resolve_, _reject) => {
// Use a lambda here, because `setResolve` accepts either new state or
// a function taking state and returning new state. If passed a
// function, `setResolve` calls it.
setResolve(() => resolve_);
});
setContent(content);
return p;
};
}, []);

useEffect(() => {
return () => {
if (resolve !== null) {
// Cancel outstanding request. This is a no-op if resolve was already called.
resolve(false);
}
};
}, [resolve]);

const element = (
<ModalConfirmDialog
open={open}
onConfirm={() => {
if (resolve) {
resolve(true);
}
setConfirmed(true);
setOpen(false);
setResolve(null);
}}
onCancel={() => {
if (resolve) {
resolve(false);
}
setOpen(false);
setResolve(null);
}}
header={content.header}
>
{content.body}
</ModalConfirmDialog>
);

return { element, confirmed, trigger, isOpen: open };
}

0 comments on commit 6974f32

Please sign in to comment.