The neatest way to handle alert dialogs in React 🥰
Tags: react, typescript, javascript, architecture
Published: 2019-07-26 18:55:38

*Time to read — 5 mins*  ☕️ 

Hola! Lazy dev here and we will talk about handling dialog alerts in react without tears 😢. If you are tired of tons of copy-pastes just to create new freaking «one question» modal dialog — prepare your coffee we are starting.
 
## The goal 

We want to make the neatest solution for displaying an alert. Pretty similar to what we have in a browser with a native `alert` function.

```javascript
const isConfirmed = alert("Are you sure you want to remove this burrito?");

if (isConfirmed) {
  await api.deleteThisAwfulBurrito();
}
```

## Sneak peek

Finally we will get to something like this. 

```typescript
const YourAwesomeComponent = () => {
  const confirm = useConfirmation()

  confirm({
    variant: "danger",
    title: "Are you sure you want to remove this burrito?",
    description: "If you will remove this burrito you will regret it 😡!!"
  }).then(() => {
    api.deleteThisAwfulBurrito();
  });
}

```

Interested? Let's write some code. 

First of all, we need to start with creating actually the modal dialog. This is just a simple alert dialog built with ❤️ and [material-ui](https://material-ui.com/components/dialogs/#dialogs)

```tsx
import {
  Button,
  Dialog,
  DialogTitle,
  DialogContent,
  DialogContentText,
  DialogActions,
} from "@material-ui/core";

export const ConfirmationDialog = ({
  open,
  title,
  variant,
  description,
  onSubmit,
  onClose
}) => {
  return (
    <Dialog open={open}>
      <DialogTitle>{title}</DialogTitle>
      <DialogContent>
        <DialogContentText>{description}</DialogContentText>
      </DialogContent>
      <DialogActions>
        <Button color="primary" onClick={onSubmit}>
          YES, I AGREE
        </Button>
        <Button color="primary" onClick={onClose} autoFocus>
          CANCEL
        </Button>
      </DialogActions>
    </Dialog>
  );
};

```

OK, but how we will adopt it to be working dynamically? That's an interesting thing to consider. Why do we need **a lot of dialogs** for each component if the user can see **only one** alert dialog simultaneously?

> If you are showing an alert dialog over the other alert dialog...you probably need to reconsider the UX part of your application.
![No god please no image](https://github.com/dmtrKovalenko/images/blob/master/no_god_pls_no.gif?raw=true)

So here we go. Everything we need is to render only **1 top-level** modal at the root of our application and show it when we need to. We'll use the power of react hooks to make it looks gracefully.

## Wrap the context

Let's create a new context instance and wrap our component tree with it. Also, create a simple state that will save the currently displaying options for the alert (like title, description and everything you need). 

```tsx
interface ConfirmationOptions {
  title: string;
  description: string;
}

const ConfirmationServiceContext = React.createContext<
  // we will pass the openning dialog function directly to consumers
  (options: ConfirmationOptions) => Promise<void>
>(Promise.reject);

export const ConfirmationServiceProvider= ({ children }) => {
  const [
    confirmationState,
    setConfirmationState
  ] = React.useState<ConfirmationOptions | null>(null);

  const openConfirmation = (options: ConfirmationOptions) => {
    setConfirmationState(options);
    return Promise.resolve()
  };

  return (
    <>
      <ConfirmationServiceContext.Provider
        value={openConfirmation}
        children={children}
      />

      <Dialog open={Boolean(confirmationState)} {...confirmationState} />
    </>
  );
};

```

Now our dialog will be opened once we connect any consumer and call the provided function. 

## Resolve confirmation

And now we need to somehow deal with closing dialog and getting a callback from the consumers. Here was used `Promise` based API, but it is possible to make it works using a callback style. In this example, once the user accepted or canceled the alert, your awaiting promise will be resolved or rejected.

To do so we need to save `Promise`'s resolving functions and call them on appropriate user action. React's `ref` is the best place for that.


```tsx
  const awaitingPromiseRef = React.useRef<{
    resolve: () => void;
    reject: () => void;
  }>();

  const openConfirmation = (options: ConfirmationOptions) => {
    setConfirmationState(options);
    return new Promise((resolve, reject) => {
      // save the promise result to the ref
      awaitingPromiseRef.current = { resolve, reject };
    });
  };

  const handleClose = () => {
    // Mostly always you don't need to handle canceling of alert dialog
    // So shutting up the unhandledPromiseRejection errors 
    if (confirmationState.catchOnCancel && awaitingPromiseRef.current) {
      awaitingPromiseRef.current.reject();
    }

    setConfirmationState(null);
  };
  
  const handleSubmit = () => {
    if (awaitingPromiseRef.current) {
      awaitingPromiseRef.current.resolve();
    }
  
    setConfirmationState(null);
  };
  
```

That's it! Our dialog machine is almost ready! One thing is left — create a custom hook for better readability 


```tsx
export const useConfirmationService = () =>
  React.useContext(ConfirmationServiceContext);
```

### Customization 

You can easily customize dialog content by passing additional `variant` prop. Just add it to the `ConfirmationOptions`

```tsx
export interface ConfirmationOptions {
  variant: "danger" | "info";
  title: string;
  description: string;
}

```

And render different dialog content as you wish.

```tsx
  <DialogActions>
    {variant === "danger" && (
      <>
        <Button color="primary" onClick={onSubmit}>
          Yes, I agree
        </Button>
        <Button color="primary" onClick={onClose} autoFocus>
          CANCEL
        </Button>
      </>
    )}

    {variant === "info" && (
      <Button color="primary" onClick={onSubmit}>
        OK
      </Button>
    )}
  </DialogActions>
```

## Are you ready?! 
![Are you ready kids?!](https://github.com/dmtrKovalenko/images/blob/master/hey%20children.jpg?raw=true)


Here is the final working example. Feel free to just steal the implementation of `ConfirmationService.tsx` file if you want to. This is pretty standalone and isolated logic of what we were talking about. 

{% codesandbox neat-dialogs-3h5ou %}

> P.S. No burritos were harmed in the making of this article