Using modal dialogs in React via useModal hook. As simple as possible.
Tags: react, javascript, frontend, usemodal
Published: 2022-05-10 07:43:47

The easiest way to interact with the user through modal dialogs is to use browser APIs like `window.alert`,  `window.confirm`,  `window.prompt`. There is only one problem. You cannot customize their appearance.

```html
<body>
  <div>
    <button class="trigger">I want to drink something!</button>
    <div class="result"></div>
  </div>
  <script>
    const resultElement = document.querySelector(".result");
    const setDrink = (drink) => {
      if (drink) {
        resultElement.innerText = `Enjoy your drink, ${drink}`;
      } else {
        resultElement.innerText = ``;
      }
    };

    document.querySelector(".trigger").addEventListener("click", () => {
      setDrink(null);

      const coffee = window.confirm("Do you like coffee?");
      if (coffee) return setDrink("Coffee");

      const tea = window.confirm("Do you like tea?");
      if (tea) return setDrink("Tea");

      setDrink("Tequila");
    });
  </script>
</body>
```

Let's imagine we do it in (almost) the same way, but using `React`:

```jsx
const OrderDrink = () => {
  const [drink, setDrink] = useState();
  const modal = useModal();

  return (
    <div>
      <button
        onClick={async () => {
          setDrink(null);

          const coffee = await modal.open("Do you like coffee?");
          if (coffee) return setDrink("Coffee");

          const tea = await modal.open("Do you like tea?");
          if (tea) return setDrink("Tea");

          return setDrink("Tequila");
        }}
      >
        I want to drink something!
      </button>
      <div>{drink && `${drink} is your drink!`}</div>
      <ConfirmModal {...modal} />
    </div>
  );
}
```

Looks good, doesn't it. The magic happens inside the `useModal` hook. Just create `Promise` inside the `open` function. And resolve it inside the `close` function with the result (payload) of the user interaction.

```jsx
const useModal = (initial = {}) => {
  const state = useMemo(() => ({ resolve: null, data: initial.data }), []);
  const [opened, setOpened] = useState(initial.opened);

  const open = useCallback((data) => {
    setOpened(true);
    state.data = data;
    return new Promise((resolve) => { state.resolve = resolve; });
  }, []);

  const close = useCallback((payload) => {
    setOpened(false);
    state.resolve(payload);
  }, []);

  return { opened, data: state.data, close, open };
};
```

And one more step - the modal dialog component:

```jsx
const ConfirmModal = (props) => {
  if (!props.opened) {
    return null;
  }

  return (
    <aside>
      <div>
        {props.data}
        <br />
        <button onClick={() => props.close(true)}>yes</button>
        <button onClick={() => props.close(false)}>no</button>
      </div>
    </aside>
  );
};
```

You can create a set of some reusable dialogs: `AlertModal`, `ConfirmModal`, `PromptModal`. And depending on specific use cases, it could be a dialog with domain specific logic like `EditProductModal`.

Source: https://gist.github.com/dsvgit/2f0a34201d1b075fdd42d2a79d5c5fc8
Sandbox: https://codesandbox.io/s/use-modal-hook-5nnr1v