'How would you make an async/await function to show a dialog in React?

I have an app working with react-modal where it brings up a dialog, but you have to embed the dialog component in the parent form, and the code gets scattered around the parent component.

So I'd like to have all the dialog code in a separate file and show it with an async/await call.

The user could click a button to bring up the dialog, enter a value, click OK, and have something written to a database - e.g.

async function clickItem() {
  const ret = await getValue(initialValue)
  if (ret.ok) {
    // ret.value has new value - update db etc
  } else {
    // user hit cancel
  }
}

Some requirements -

  • write dialog as function component, with useState hooks etc, so can be fully dynamic
  • pass in initial field values to wrapper function
  • wrapper function returns a promise that resolves with an object {ok:true/false, relevant field values...}
  • keep dialog code encapsulated in one file
  • no need to embed component in parent component as with react-modal - just call a fn to have it rendered and inserted into DOM
  • testable component with jest and/or cypress

There's a project react-confirm-alert, which nearly does what I'd like, but you can't use useState with it, nor pass it a React Component, so is limited to static dialogs.

So how would you go about doing something like this?



Solution 1:[1]

I wound up getting a simple version working with no styling - here's a Code Sandbox - https://codesandbox.io/s/react-async-dialog-3xdx7.

dialog

The wrapper function just creates a Promise and passes the resolve callback down to the dialog component, which calls resolve when the user hits OK or Cancel.

If put all into one file it looks like this -

import React from "react";
import ReactDOM from "react-dom";

function App() {
  async function clickGetValue() {
    const ret = await getValue(3);
    console.log(ret);
  }
  return (
    <div className="App">
      <button onClick={clickGetValue}>Get Value</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);


function GetValue({ resolve, initialValue = 0 }) {
  const [value, setValue] = React.useState(initialValue);
  function clickPlus() {
    setValue(value => value + 1);
  }
  function clickMinus() {
    setValue(value => value - 1);
  }
  function clickOK() {
    removeDialog();
    resolve({ ok: true, value });
  }
  function clickCancel() {
    removeDialog();
    resolve({ ok: false });
  }
  return (
    <div id="getValue">
      <span id="getValue-value">{value}</span>
      <button onClick={clickPlus}>+1</button>
      <button onClick={clickMinus}>-1</button>
      <button onClick={clickOK}>OK</button>
      <button onClick={clickCancel}>Cancel</button>
    </div>
  );
}

export default function getValue(initialValue) {
  return new Promise((resolve, reject) => {
    addDialog(initialValue, resolve);
  });
}

function addDialog(initialValue, resolve) {
  const body = document.getElementsByTagName("body")[0];
  const div = document.createElement("div");
  div.setAttribute("id", "getValue-container");
  body.appendChild(div);
  ReactDOM.render(
    <GetValue initialValue={initialValue} resolve={resolve} />,
    div
  );
}

function removeDialog() {
  const div = document.getElementById("getValue-container");
  const body = document.getElementsByTagName("body")[0];
  body.removeChild(div);
}

Solution 2:[2]

You can install use-awaitable-component from npm. Then you can use modal, popup, or anything, like this:

import { useState } from "react";
import useAwaitableComponent from "use-awaitable-component";
import "./styles.css";

function Modal({ visible, onSubmit, onCancel }) {
  const [text, setText] = useState("");
  const display = visible ? "block" : "none";

  const handleSubmit = () => {
    onSubmit(text);
    setText("");
  };

  const handleCancel = () => {
    onCancel(":)");
  };

  return (
    <div className="modal" style={{ display }}>
      <div className="modal-head">This is a modal</div>
      <div className="modal-body">
        <input
          placeholder="Type something here..."
          value={text}
          onChange={(e) => setText(e.target.value)}
        />
        <button onClick={handleSubmit}>Submit</button>
      </div>
      <button className="modal-close" onClick={handleCancel}>
        X
      </button>
    </div>
  );
}

export default function App() {
  const [status, execute, resolve, reject, reset] = useAwaitableComponent();
  const showModal = status === "awaiting";

  const handleAwaitModal = async () => {
    try {
      const value = await execute();
      alert(`VALUE: ${value}`);
    } catch (err) {
      alert(`Canceled: ${err}`);
    } finally {
      reset();
    }
  };
  return (
    <div className="App">
      <div className="button-container">
        <button disabled={showModal} onClick={handleAwaitModal}>
          {showModal ? "Waiting..." : "Show Modal"}
        </button>
      </div>
      <Modal visible={showModal} onSubmit={resolve} onCancel={reject} />
    </div>
  );
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Brian Burns
Solution 2 Muhammad Irvan Hermawan