Suppose you have a React component that displays a modal dialog and returns the result:
1 |
<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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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:
1 2 3 |
if(confirm("Are you sure?") === true) { // do something... } |
Let’s implement a hook that wraps the dialog component in a promise!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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! :-)