What are the use cases that come to our mind when we hear of promises?
- Handling async operations.
- Handling some task that will finish in future.
- Doing something like network or DB calls.
But that's not all to it, we can use it to handle user interactions as well. Let us see how.
Motivation
So, I was building simple confirmation modal for my React project. It needed to be as simple as possible. A modal with some confirmation text in it and two buttons for accepting or declining. Something like this
The simple/straight-forward approach
- Make modal component.
- Mount it conditionally using state.
- Handle delete operation inside Modal component.
The code for it looks something like
import { useState } from "react";
const Modal = ({ setShowModal, handleDelete }) => {
return (
<dialog open>
<p>Are you sure you want to delete?</p>
<button
onClick={() => {
setShowModal(false);
handleDelete();
}}
>
Yes
</button>
<button
onClick={() => {
setShowModal(false);
}}
>
No
</button>
</dialog>
);
};
function App() {
const [showModal, setShowModal] = useState(false);
const handleDelete = () => {
//perform delete operation here...
console.log("hey");
};
return (
<div>
{showModal && (
<Modal setShowModal={setShowModal} handleDelete={handleDelete} />
)}
<button onClick={() => setShowModal(true)}>Delete</button>
</div>
);
}
export default App;
Did you notice the problem?
The above code works all fine, however there are few issues
- We need to maintain a state for showing/hiding modal and pass the setter function to modal.
- We need to pass the business logic to the modal(delete handler).
- The flow of code is not linear anymore, the deletion is being handled by some other component away from the main component(App)
- Not clean code.
The idea of asynchronicity
It was this moment when I felt there must be a better way to handle this. I noticed that a click operation by user is also an async operation! It can happen anytime in future. This is where the idea of Promise kicked in.
The better way
- We can make some custom hook from where a promise is returned which can be awaited upon.
- The promise can resolve to either true or false which can show if the user accepts or rejects.
- The custom hook can also return a Modal component which can be directly use in at the place of application.
- We can also make custom hook configurable to accept different messages.
A Custom hook
import { useState } from "react";
const useConfirmation = () => {
const [modalState, setModalState] = useState({
showModal: false,
resolve: null,
message: "",
});
const Modal = () => {
return (
<dialog open={modalState.showModal}>
<div>
{modalState.message}
<button
onClick={() => {
setModalState((prev) => ({
...prev,
showModal: false,
}));
modalState.resolve(false);
}}
>
Close
</button>
<button
onClick={() => {
setModalState((prev) => ({
...prev,
showModal: false,
}));
modalState.resolve(true);
}}
>
Confirm
</button>
</div>
</dialog>
);
};
const confirm = (message) => {
return new Promise((resolve) => {
setModalState((prev) => ({
resolve: resolve,
showModal: true,
message: message,
}));
});
};
return { Modal, confirm };
};
export { useConfirmation };
The Modal component has it's own state which stores the message, resolve function returned by promise and show state.
Application
import { useConfirmation } from "./useConfirmation.jsx";
function App() {
const { Modal, confirm } = useConfirmation();
const handleDelete = async () => {
const isItOkToDelete = await confirm("Are you sure you want to delete?");
console.log(isItOkToDelete);
};
return (
<div>
{Modal()}
<button onClick={handleDelete}>Delete</button>
</div>
);
}
export default App;
The useConfirmation
hook returns a function which returns a promise. The function also takes in a message argument to display inside modal. We can await this promise to either receive true/false and proceed with the code flow ahead.
Thank You
Please do share you feedback and comments. Hope you like it, have a good day.
Top comments (0)