DEV Community

Frulow
Frulow

Posted on

Asking For Confirmation Inline In React Function

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)} 
       />
)

Enter fullscreen mode Exit fullscreen mode

image from google

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);

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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> )
}

Enter fullscreen mode Exit fullscreen mode

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)...

Enter fullscreen mode Exit fullscreen mode

And here you have a re-usable hooks to call inline in a function from anywhere

Top comments (0)