Skip to content

Instantly share code, notes, and snippets.

@rodrigonehring
Last active May 19, 2022 21:45
Show Gist options
  • Save rodrigonehring/4d08c478abbd5e07a1f581e7bb1d7e8c to your computer and use it in GitHub Desktop.
Save rodrigonehring/4d08c478abbd5e07a1f581e7bb1d7e8c to your computer and use it in GitHub Desktop.
mui/material v5 - confirmation dialog returns promise on open
import React from 'react';
import { fireEvent, render, screen, waitFor } from '@tests/utils';
import ConfirmationDialog, { useConfirmationDialog } from './ConfirmationDialog';
function fakePromise() {
let resolve;
const promise = new Promise((promiseResolve) => {
resolve = promiseResolve;
});
return { promise, resolve };
}
function MockComponent({ action }) {
const [result, setResult] = React.useState(null);
const confirmSomething = useConfirmationDialog();
const handleDelete = async () => {
const response = await confirmSomething.open(action);
setResult(response);
};
if (result !== null) {
return <span>your result is: {result ? 'confirmed' : 'cancelled'}</span>;
}
return (
<div>
<button type="button" onClick={handleDelete}>
delete everything
</button>
<ConfirmationDialog {...confirmSomething} title="have you finished" message="are you sure?" />
</div>
);
}
const key = 'confirm dialog for have you finished';
describe('<ConfirmationDialog />', () => {
it('renders properly and confirm', async () => {
render(<MockComponent />);
let dialog = screen.queryByLabelText(key);
expect(dialog).not.toBeInTheDocument();
fireEvent.click(screen.getByText('delete everything'));
dialog = screen.queryByLabelText(key);
expect(dialog).toBeInTheDocument();
fireEvent.click(screen.getByText('Confirm'));
await waitFor(() => {
expect(screen.getByText('your result is: confirmed')).toBeInTheDocument();
});
});
it('renders properly and cancel', async () => {
render(<MockComponent />);
fireEvent.click(screen.getByText('delete everything'));
fireEvent.click(screen.getByText('Cancel'));
await waitFor(() => {
expect(screen.getByText('your result is: cancelled')).toBeInTheDocument();
});
});
it('call action and put a loading', async () => {
let promise;
const action = () => {
promise = fakePromise();
return promise.promise;
};
render(<MockComponent action={action} />);
fireEvent.click(screen.getByText('delete everything'));
fireEvent.click(screen.getByText('Confirm'));
expect(screen.getByText('Confirm')).toBeDisabled();
promise.resolve();
await waitFor(() => {
expect(screen.queryByLabelText(key)).not.toBeInTheDocument();
expect(screen.getByText('your result is: confirmed')).toBeInTheDocument();
});
});
});
import React, { useState } from 'react';
import {
Dialog,
Button,
DialogTitle,
DialogActions,
DialogContent,
Typography,
} from '@mui/material';
import LoadingButton from '@mui/lab/LoadingButton';
type Props = {
isOpen: boolean;
onResolve: (result: boolean) => void;
title: string;
message: React.ReactNode;
confirmText?: string;
cancelText?: string;
loading?: boolean;
};
type Action = () => Promise<any>;
type State = {
resolvePromise?: (result: boolean) => void;
isOpen: boolean;
loading?: boolean;
action?: Action;
};
const initialState: State = { isOpen: false };
export function useConfirmationDialog() {
const [state, setState] = useState(initialState);
return {
isOpen: Boolean(state.isOpen),
loading: Boolean(state.loading),
async onResolve(result: boolean) {
if (state.action && result) {
try {
setState({ ...state, loading: true });
await state.action();
} catch (error) {
setState({ ...state, loading: false });
}
}
if (state.resolvePromise) state.resolvePromise(result);
setState(initialState);
},
open(action?: Action) {
return new Promise((resolve) => {
setState({ resolvePromise: resolve, isOpen: true, action });
});
},
};
}
export default function DialogWarning(props: Props) {
const { isOpen, onResolve, loading, title, message, confirmText, cancelText } = props;
return (
<Dialog
open={isOpen}
onClose={() => !loading && onResolve(false)}
maxWidth="xs"
aria-label={`confirm dialog for ${title}`}
sx={{ '& .MuiPaper-root': { maxWidth: 480, px: 1.5, py: 2 } }}
>
<DialogTitle>
<Typography variant="h3" component="span">
{title}
</Typography>
</DialogTitle>
<DialogContent>{message}</DialogContent>
<DialogActions>
<Button color="secondary" onClick={() => onResolve(false)} disabled={loading}>
{cancelText || 'Cancel'}
</Button>
<LoadingButton
color="secondary"
onClick={() => onResolve(true)}
loading={loading}
disabled={loading}
variant="contained"
>
{confirmText || 'Confirm'}
</LoadingButton>
</DialogActions>
</Dialog>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment