Skip to content

Commit

Permalink
Always resolve the promise with confirmation status and close reason
Browse files Browse the repository at this point in the history
  • Loading branch information
jonatanklosko committed Feb 5, 2025
1 parent 11c7311 commit 0b31438
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 143 deletions.
86 changes: 45 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,17 @@ import { useConfirm } from "material-ui-confirm";
const Item = () => {
const confirm = useConfirm();

const handleClick = () => {
confirm({ description: "This action is permanent!" })
.then(() => {
/* ... */
})
.catch(() => {
/* ... */
});
const handleClick = async () => {
const { confirmed, reason } = await confirm({
description: "This action is permanent!",
});

if (confirmed) {
/* ... */
}

console.log(reason);
//=> "confirm" | "cancel" | "natural" | "unmount"
};

return <Button onClick={handleClick}>Click</Button>;
Expand All @@ -66,42 +69,43 @@ This component is required in order to render a dialog in the component tree.

##### Props

| Name | Type | Default | Description |
| -------------------- | -------- | ------- | ----------------------------------------------------------------------- |
| **`defaultOptions`** | `object` | `{}` | Overrides the default options used by [`confirm`](#useconfirm-confirm). |
| Name | Type | Default | Description |
| --------------------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`defaultOptions`** | `object` | `{}` | Overrides the default options used by [`confirm`](#useconfirm-confirm). |
| **`useLegacyReturn`** | `boolean` | `false` | When set to `true`, restores the `confirm` behaviour from v3: the returned promise is resolved on confirm, rejected on cancel, and kept pending on natural close. |

#### `useConfirm() => confirm`

This hook returns the `confirm` function.

#### `confirm([options]) => Promise`
#### `confirm([options]) => Promise<{ confirmed: boolean; reason: "confirm" | "cancel" | "natural" | "unmount"; }>`

This function opens a confirmation dialog and returns a promise
representing the user choice (resolved on confirmation and rejected on cancellation).

##### Options

| Name | Type | Default | Description |
| --------------------------------------- | ----------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`title`** | `ReactNode` | `'Are you sure?'` | Dialog title. |
| **`description`** | `ReactNode` | `''` | Dialog content, automatically wrapped in `DialogContentText`. |
| **`content`** | `ReactNode` | `null` | Dialog content, same as `description` but not wrapped in `DialogContentText`. Supersedes `description` if present. |
| **`confirmationText`** | `ReactNode` | `'Ok'` | Confirmation button caption. |
| **`cancellationText`** | `ReactNode` | `'Cancel'` | Cancellation button caption. |
| **`dialogProps`** | `object` | `{}` | Material-UI [Dialog](https://mui.com/material-ui/api/dialog/#props) props. |
| **`dialogActionsProps`** | `object` | `{}` | Material-UI [DialogActions](https://mui.com/material-ui/api/dialog-actions/#props) props. |
| **`confirmationButtonProps`** | `object` | `{}` | Material-UI [Button](https://mui.com/material-ui/api/button/#props) props for the confirmation button. |
| **`cancellationButtonProps`** | `object` | `{}` | Material-UI [Button](https://mui.com/material-ui/api/dialog/#props) props for the cancellation button. |
| **`titleProps`** | `object` | `{}` | Material-UI [DialogTitle](https://mui.com/api/dialog-title/#props) props for the dialog title. |
| **`contentProps`** | `object` | `{}` | Material-UI [DialogContent](https://mui.com/api/dialog-content/#props) props for the dialog content. |
| **`allowClose`** | `boolean` | `true` | Whether natural close (escape or backdrop click) should close the dialog. When set to `false` force the user to either cancel or confirm explicitly. |
| **`confirmationKeyword`** | `string` | `undefined` | If provided the confirmation button will be disabled by default and an additional textfield will be rendered. The confirmation button will only be enabled when the contents of the textfield match the value of `confirmationKeyword` |
| **`confirmationKeywordTextFieldProps`** | `object` | `{}` | Material-UI [TextField](https://mui.com/material-ui/api/text-field/) props for the confirmation keyword textfield. |
| **`acknowledgement`** | `string` | `undefined` | If provided shows the acknowledge checkbox with this string as checkbox label and disables the confirm button while the checkbox is unchecked. |
| **`acknowledgementFormControlLabelProps`** | `object` | `{}` | Material-UI [FormControlLabel](https://mui.com/material-ui/api/form-control-label/#props) props for the form control label. |
| **`acknowledgementCheckboxProps`** | `object` | `{}` | Material-UI [Checkbox](https://mui.com/material-ui/api/checkbox/#props) props for the acknowledge checkbox. |
| **`hideCancelButton`** | `boolean` | `false` | Whether to hide the cancel button. |
| **`buttonOrder`** | `string[]` | `["cancel", "confirm"]` | Specify the order of confirm and cancel buttons. |
| Name | Type | Default | Description |
| ------------------------------------------ | ----------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`title`** | `ReactNode` | `'Are you sure?'` | Dialog title. |
| **`description`** | `ReactNode` | `''` | Dialog content, automatically wrapped in `DialogContentText`. |
| **`content`** | `ReactNode` | `null` | Dialog content, same as `description` but not wrapped in `DialogContentText`. Supersedes `description` if present. |
| **`confirmationText`** | `ReactNode` | `'Ok'` | Confirmation button caption. |
| **`cancellationText`** | `ReactNode` | `'Cancel'` | Cancellation button caption. |
| **`dialogProps`** | `object` | `{}` | Material-UI [Dialog](https://mui.com/material-ui/api/dialog/#props) props. |
| **`dialogActionsProps`** | `object` | `{}` | Material-UI [DialogActions](https://mui.com/material-ui/api/dialog-actions/#props) props. |
| **`confirmationButtonProps`** | `object` | `{}` | Material-UI [Button](https://mui.com/material-ui/api/button/#props) props for the confirmation button. |
| **`cancellationButtonProps`** | `object` | `{}` | Material-UI [Button](https://mui.com/material-ui/api/dialog/#props) props for the cancellation button. |
| **`titleProps`** | `object` | `{}` | Material-UI [DialogTitle](https://mui.com/api/dialog-title/#props) props for the dialog title. |
| **`contentProps`** | `object` | `{}` | Material-UI [DialogContent](https://mui.com/api/dialog-content/#props) props for the dialog content. |
| **`allowClose`** | `boolean` | `true` | Whether natural close (escape or backdrop click) should close the dialog. When set to `false` force the user to either cancel or confirm explicitly. |
| **`confirmationKeyword`** | `string` | `undefined` | If provided the confirmation button will be disabled by default and an additional textfield will be rendered. The confirmation button will only be enabled when the contents of the textfield match the value of `confirmationKeyword` |
| **`confirmationKeywordTextFieldProps`** | `object` | `{}` | Material-UI [TextField](https://mui.com/material-ui/api/text-field/) props for the confirmation keyword textfield. |
| **`acknowledgement`** | `string` | `undefined` | If provided shows the acknowledge checkbox with this string as checkbox label and disables the confirm button while the checkbox is unchecked. |
| **`acknowledgementFormControlLabelProps`** | `object` | `{}` | Material-UI [FormControlLabel](https://mui.com/material-ui/api/form-control-label/#props) props for the form control label. |
| **`acknowledgementCheckboxProps`** | `object` | `{}` | Material-UI [Checkbox](https://mui.com/material-ui/api/checkbox/#props) props for the acknowledge checkbox. |
| **`hideCancelButton`** | `boolean` | `false` | Whether to hide the cancel button. |
| **`buttonOrder`** | `string[]` | `["cancel", "confirm"]` | Specify the order of confirm and cancel buttons. |

## Useful notes

Expand All @@ -117,14 +121,14 @@ naturally triggers a click.
const MyComponent = () => {
// ...

const handleClick = () => {
confirm({ confirmationButtonProps: { autoFocus: true } })
.then(() => {
/* ... */
})
.catch(() => {
/* ... */
});
const handleClick = async () => {
const { confirmed } = await confirm({
confirmationButtonProps: { autoFocus: true },
});

if (confirmed) {
/* ... */
}
};

// ...
Expand Down
52 changes: 40 additions & 12 deletions src/ConfirmProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ const buildOptions = (defaultOptions, options) => {

let confirmGlobal;

const ConfirmProvider = ({ children, defaultOptions = {} }) => {
const ConfirmProvider = ({
children,
defaultOptions = {},
useLegacyReturn = false,
}) => {
// State that we clear on close (to avoid dangling references to resolve and
// reject). If this is null, the dialog is closed.
const [state, setState] = useState(null);
Expand All @@ -94,17 +98,38 @@ const ConfirmProvider = ({ children, defaultOptions = {} }) => {
const [options, setOptions] = useState({});
const [key, setKey] = useState(0);

const confirmBase = useCallback((parentId, options = {}) => {
return new Promise((resolve, reject) => {
setKey((key) => key + 1);
setOptions(options);
setState({ resolve, reject, parentId });
});
}, []);
const confirmBase = useCallback(
(parentId, options = {}) => {
const promise = new Promise((resolve, _reject) => {
setKey((key) => key + 1);
setOptions(options);
setState({ resolve, parentId });
});

// Converts the promise into the legacy promise from v3
if (useLegacyReturn) {
return new Promise((resolve, reject) => {
promise.then(({ confirmed, reason }) => {
if (confirmed === true && reason === "confirm") {
resolve();
}

if (confirmed === false && reason === "cancel") {
reject();
}
});
});
}

return promise;
},
[useLegacyReturn],
);

const closeOnParentUnmount = useCallback((parentId) => {
setState((state) => {
if (state && state.parentId === parentId) {
state && state.resolve({ confirmed: false, reason: "unmount" });
return null;
} else {
return state;
Expand All @@ -113,26 +138,29 @@ const ConfirmProvider = ({ children, defaultOptions = {} }) => {
}, []);

const handleClose = useCallback(() => {
setState(null);
setState((state) => {
state && state.resolve({ confirmed: false, reason: "natural" });
return null;
});
}, []);

const handleCancel = useCallback(() => {
setState((state) => {
state && state.reject();
state && state.resolve({ confirmed: false, reason: "cancel" });
return null;
});
}, []);

const handleConfirm = useCallback(() => {
setState((state) => {
state && state.resolve();
state && state.resolve({ confirmed: true, reason: "confirm" });
return null;
});
}, []);

confirmGlobal = useCallback((options) => {
return confirmBase("global", options);
});
}, []);

return (
<Fragment>
Expand Down
12 changes: 10 additions & 2 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,17 @@ export interface ConfirmOptions {
export interface ConfirmProviderProps {
children: React.ReactNode;
defaultOptions?: ConfirmOptions;
useLegacyReturn?: boolean;
}

export interface ConfirmResult {
confirmed: boolean;
reason: "confirm" | "cancel" | "natural" | "unmount";
}

export const ConfirmProvider: React.ComponentType<ConfirmProviderProps>;

export const useConfirm: () => (options?: ConfirmOptions) => Promise<void>;
export const confirm: (options?: ConfirmOptions) => Promise<void>;
export const useConfirm: () => (
options?: ConfirmOptions,
) => Promise<ConfirmResult>;
export const confirm: (options?: ConfirmOptions) => Promise<ConfirmResult>;
Loading

0 comments on commit 0b31438

Please sign in to comment.