DEV Community

devterminal
devterminal

Posted on

3 2

Using modal dialogs in React via useModal hook. As simple as possible.

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.

<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>
Enter fullscreen mode Exit fullscreen mode

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

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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.

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 };
};
Enter fullscreen mode Exit fullscreen mode

And one more step - the modal dialog component:

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

SurveyJS custom survey software

JavaScript Form Builder UI Component

Generate dynamic JSON-driven forms directly in your JavaScript app (Angular, React, Vue.js, jQuery) with a fully customizable drag-and-drop form builder. Easily integrate with any backend system and retain full ownership over your data, with no user or form submission limits.

Learn more