Suppose a situation in React where you want to ask for confirmation from the user.
For example, you want to show them a custom popup asking if they really want to delete something or not. This is a
situation I am going to cover.
Let's for example, take a function onDeletePost
. Which is called to delete a post. Hence, we are asking for a confirmation from the user, if they intend to do so.
In code we do so like:
const [showPopup, setShowPopup] = useState<boolean>(false)
const onDeletePost = async (id: string) => {
try {
if (!showPopup) {
setShowPopup(true)
return;
}
setShowPopup(false)
await // call_api()
} catch (error: any) {
...
}
};
return (
<AskConfirmationPopup
show={showPopup}
onConfirmed={onDeletePost}
onCancel={() => setShowPopup(false)}
/>
)
You do not want to do that everywhere, in each and every component, so let's make it re-usable.
// confirmContext.tsx
import {
PropsWithChildren,
createContext,
useCallback,
useContext,
useMemo,
useState,
} from 'react';
import ConfirmBox, { PromptConfirmOptions, PromptConfirmProps } from '@components/UI/PromptConfirm';
export const ConfirmContext = createContext<{
askConfirm: (
title: PromptConfirmProps['title'],
message: PromptConfirmProps['description'],
options?: Partial<PromptConfirmOptions>
) => Promise<boolean>;
}>({
askConfirm: async () => false,
});
export const ConfirmProvider = ({ children }: PropsWithChildren) => {
const [open, setOpen] = useState(false);
const [title, setTitle] = useState<PromptConfirmProps['title']>('');
const [message, setMessage] = useState<PromptConfirmProps['description']>('');
const [options, setOptions] = useState<Partial<PromptConfirmOptions>>({});
const [resolve, setResolve] = useState<(value: boolean) => void>(() => {
//anything
});
const confirm = useCallback(
async (
title: PromptConfirmProps['title'],
message: PromptConfirmProps['description'],
options?: Partial<PromptConfirmOptions>
) =>
new Promise<boolean>((res) => {
setOpen(true);
setTitle(title);
setMessage(message);
setOptions(options ?? {});
setResolve(() => res);
}),
[]
);
const handleAction = (confirmed: boolean) => {
setOpen(false);
resolve(confirmed);
};
const values = useMemo(() => {
return { askConfirm: confirm };
}, [confirm]);
return (
<ConfirmContext.Provider value={values}>
{children}
{open && (
<ConfirmBox title={title} description={message} onAction={handleAction} options={options} />
)}
</ConfirmContext.Provider>
);
};
export const useConfirmBox = () => useContext(ConfirmContext);
I am using @mui/material
(basically material ui component library
// PromptConfirm.tsx - The main Popup Component To show
import * as React from 'react';
import { ThemeProvider, Typography } from '@mui/material';
import Button, { ButtonProps } from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText, { DialogContentTextProps } from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import lightTheme from '@styles/theme/lightTheme';
export interface PromptConfirmOptions {
acceptButtonProps: ButtonProps;
rejectButtonProps: ButtonProps;
descriptionProps: DialogContentTextProps;
}
export interface PromptConfirmProps {
title: React.ReactElement | string;
description: React.ReactElement | string;
options: Partial<PromptConfirmOptions>;
onAction: (proceed: boolean) => void;
}
function ConfirmBox({ title, description, onAction, options }: PromptConfirmProps) {
const _handleCancel = () => onAction(false);
const _handleOkay = () => onAction(true);
return (
<ThemeProvider theme={lightTheme}>
<Dialog
open
onClose={_handleCancel}
aria-labelledby='alert-dialog-title'
aria-describedby='alert-dialog-description'
PaperProps={{ sx: { borderRadius: 3, p: 1, maxWidth: '20em' } }}
>
<DialogTitle id='alert-dialog-title'>
<Typography variant='h5' sx={{ fontWeight: 700 }}>
{title}
</Typography>
</DialogTitle>
<DialogContent>
<DialogContentText id='alert-dialog-description' {...options?.descriptionProps}>
{description}
</DialogContentText>
</DialogContent>
<DialogActions sx={{ px: 3 }}>
<Button
variant='contained'
fullWidth
color='error'
{...options?.acceptButtonProps}
onClick={_handleOkay}
>
Yes
</Button>
<Button
variant='contained'
autoFocus
color='primary'
fullWidth
{...options?.rejectButtonProps}
onClick={_handleCancel}
>
No
</Button>
</DialogActions>
</Dialog>
</ThemeProvider>
);
}
export default ConfirmBox;
Wrap your root component or App.tsx
or Index.tsx
with below:
import { ConfirmProvider } from 'path/to/confirmContext.tsx';
export default function App(){
return ( <ConfirmProvider> ... </ConfirmProvider> )
}
And now use it anywhere like below
const { askConfirm } = useConfirmBox();
const onDeletePost = async (id: string) => {
try {
const confirmed = await askConfirm(
[title], // Delete now?
[description], // Are you sure?
);
if (!confirmed) return; // return if not confirmed
...
// do your thing here
}catch(erro)...
And here you have a re-usable hooks to call inline in a function from anywhere
Top comments (0)