Quantcast
Viewing all articles
Browse latest Browse all 79

Show React dialogs fluently with hooks and promises

Suppose you have a React component that displays a modal dialog and returns the result:

<Dialog message="Are you sure?" onClose={onCloseDialog}>

The typical way to use it would be to have a state variable controlling if it should be displayed or not, and a function for handling the return value. Something like this:

function MyComponent() {
  const [showDialog, setShowDialog] = useState(false);

  function onClickButton() {
    setShowDialog(true);
  }

  function onCloseDialog(result) {
    setShowDialog(false);
    console.log("Dialog Result: ", result)
  }

  return (
    <>
      <button onClick={onClickButton}>Open Dialog</button>
      {showDialog &&
         <Dialog message="Are you sure?" onClose={onCloseDialog} />        
      }
    </>
  )
}

Nothing special, but I don’t like how the logic is located in different functions instead of a continuous flow. And what if we wanted to have multiple dialogs? We would typically repeat everything for each dialog…

What if we could open the dialog in a more fluent way, similar to JavaScript confirm:

if(confirm("Are you sure?") === true) {
    // do something...
}

Let’s implement a hook that wraps the dialog component in a promise!

export function useDialog() {

  const promiseRef = useRef<{
    resolve: (value: boolean) => void;
    reject: (value: boolean) => void;
  }>();

  const [dialogJSX, setDialogJSX] = useState<JSX.Element>(<></>);

  function show(props: { message: string }) {
    setDialogJSX(<Dialog {...props} onDialogClose={onClose} />);
    return new Promise<boolean>((resolve, reject) => {
      promiseRef.current = { resolve, reject };
    });
  };

  function onClose(result: IDialogResult) {
    setDialogJSX(<></>);
    promiseRef.current!.resolve(result);  // We could also reject the promise if result is negative, but then you must catch the rejection instead of just checking the "result".
  }

  return {
    Show: show,
    JSX: dialogJSX
  }
}

Now we can open the dialog and handle the return value in a single flow, even with nested dialogs:

function MyComponent() {
  const dialog = useDialog();

  function onClickButton() {
    dialog.Show({ message: "Are you sure?"}).then(result => {
		if(result === true) {
			dialog.Show({ message: "Are you REALLY sure?"}).then(result => {
				if(result === true) DoSomething();
			});
		}
    });
  }

  return (
    <>
      <button onClick={onClickButton}>Open Dialog</button>
      {dialog.JSX}
    </>
  )
}

Pretty neat! :-)


Viewing all articles
Browse latest Browse all 79

Trending Articles