Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
March 31, 2022 02:43 am GMT

Creating a deferred promise hook in React

Hello fellow readers! In this post I am going to show how to create and use a deferred promise hook in React. Feel free to ask or give your opinion in the comments section.
It is important that you may have some knowledge about promises to fully understand this article. If you don't, please read this great article from MDN.
Let's go!

Principle

A deferred promise, defined by the jQuery lib, is:

An object that can register multiple callbacks into callback queues, invoke callback queues, and relay the success or failure state of any synchronous or asynchronous function.

In simple words, it means that we can store promise's callbacks, such as resolve and reject to use them later, deferring an action until it's done.

Use case

Let's imagine the following scenario:

  • There is a task list component that has a remove button
  • Once the remove button is clicked, a confirm dialog shows up
  • Once the user confirms the removal, the task is deleted, otherwise nothing happens

Here is a draft of this idea:

Scenario draft print screen showing a list with three tasks along with three remove buttons aside

We can build the code of this scenario as the following:

  • Task list component
type ListProps = {  allowDelete: () => Promise<boolean>;};const data = ['Task 1', 'Task 2', 'Task 3'];const List = ({ allowDelete }: ListProps) => {  const [tasks, setTasks] = useState(data);  const handleRemove = async (task: string) => {    const canDelete = await allowDelete();    if (!canDelete) return;    const newTasks = tasks.filter((innerTask) => innerTask !== task);    setTasks(newTasks);  };  return (    <ul>      {tasks.map((task) => (        <li style={{ marginBottom: 10 }}>          <span>{task}</span>          <button style={{ marginLeft: 10 }} onClick={() => handleRemove(task)}>            Remove          </button>        </li>      ))}    </ul>  );};
  • Confirm dialog
type DialogProps = {  isOpen: boolean;  handleConfirm: () => void;  handleClose: () => void;};const Dialog = ({ isOpen, handleConfirm, handleClose }: DialogProps) => {  return (    <dialog open={isOpen}>      <div>Do you really want to remove this task?</div>      <button onClick={handleConfirm}>Yes</button>      <button onClick={handleClose}>No</button>    </dialog>  );};
  • Application
const App = () => {  const [isDialogOpen, setIsDialogOpen] = useState(false);  const allowDelete = async () => {    setIsDialogOpen(true);    return true;  };  const handleConfirm = () => {    setIsDialogOpen(false);  };  const handleClose = () => {    setIsDialogOpen(false);  };  return (    <Fragment>      <List allowDelete={allowDelete} />      <Dialog        isOpen={isDialogOpen}        handleConfirm={handleConfirm}        handleClose={handleClose}      />    </Fragment>  );};

Looking at this scenario, it stays clear that the list component needs to wait for the user intervention before deciding if a task can or cannot be removed.

But, there is a problem! If we run this code, we will encounter a bug. As soon as the user clicks the remove button, the task is already deleted before the user's consent.

Gif showing the code bug. When the user clicks the remove button of a task, it is immediately removed before the user's consent.

Deferred promise to the rescue

To fix this bug, we need to tell our code to wait for the user consent, and this is possible by creating a deferred promise.
I will show step by step how to create our custom hook.

  • First, we will create a type that will hold our defer object. This object must have three properties: a resolve function, a reject function and the promise that will be fulfilled. We can note below that the DeferredPromise receives a generic type (DeferType) that infers the resolve's value type as well as the promise type.You may skip this step if you are using plain JavaScript instead of TypeScript.
type DeferredPromise<DeferType> = {  resolve: (value: DeferType) => void;  reject: (value: unknown) => void;  promise: Promise<DeferType>;};
  • Next, we are going to start to define the hook's function. This hook begins with a simple ref that will hold our defer object. Note that the hook receives the same generic type defined above.
export function useDeferredPromise<DeferType>() {  const deferRef = useRef<DeferredPromise<DeferType>>(null);  return { deferRef: deferRef.current };}
  • So far, so good! Now let's increment our hook with a function that creates the defer object. First, we will build our deferred object.
// Here is our deferred object that will hold the callbacks and the promiseconst deferred = {} as DeferredPromise<DeferType>;// We then create the main part of our defer object: the promise// Note that we take the promise's callbacks and inject them into our deferred objectconst promise = new Promise<DeferType>((resolve, reject) => {   deferred.resolve = resolve;   deferred.reject = reject;});// Finally, we inject the whole promise into the deferred objectdeferred.promise = promise;
  • Next, we will update the ref hook with the new deferred object.
deferRef.current = deferred;
  • Now we have our complete function and hook! Check it out:
export function useDeferredPromise<DeferType>() {  const deferRef = useRef<DeferredPromise<DeferType>>(null);  const defer = () => {    const deferred = {} as DeferredPromise<DeferType>;    const promise = new Promise<DeferType>((resolve, reject) => {      deferred.resolve = resolve;      deferred.reject = reject;    });    deferred.promise = promise;    deferRef.current = deferred;    return deferRef.current;  };  return { defer, deferRef: deferRef.current };}
  • Alright! Our hook is now complete. Let's now use it to solve the bug we found!

Using the deferred promise hook

Let's modify the Application component adding the new hook. Note that the allowDelete function now returns a deferred promise and the confirm/delete functions resolve this deferred promise.

const App = () => {  const [isDialogOpen, setIsDialogOpen] = useState(false);  // Here we declare the new hook  // Note that we will resolve this promise using a boolean value (`true` or `false`). This is the generic type that we defined earlier.  const { defer, deferRef } = useDeferredPromise<boolean>();  const allowDelete = async () => {    setIsDialogOpen(true);    // Now a deferred promise is being returned    return defer().promise;  };  const handleConfirm = () => {    setIsDialogOpen(false);    // If the user consents, the deferred promise is resolved with `true`    deferRef.resolve(true);  };  const handleClose = () => {    setIsDialogOpen(false);    // If the user declines, the deferred promise is resolved with `false`    deferRef.resolve(false);  };  return (    <Fragment>      <List allowDelete={allowDelete} />      <Dialog        isOpen={isDialogOpen}        handleConfirm={handleConfirm}        handleClose={handleClose}      />    </Fragment>  );};

Now if we run this code, we will note that the bug is fixed! Our code successfully waits for the user consent before removing a task. If the removal action is declined, nothing happens, as expected.

Gif showing that the bug is now fixed. The task will be remove if the user gives it's consent, otherwise nothing happens.

Wrapping up

We successfully created our deferred promise hook from scratch, and it was pretty simple!
I showed just one of the use cases that this hook might become handy, but you can use this whenever you need to wait something to happen before running an action.
Here I also leave the link for every code that was written in this article: https://stackblitz.com/edit/react-ts-sukfgm?file=index.tsx

Just one important note: once you defer a promise, never forget to resolve or reject it, otherwise you might encounter some memory leak problems.

That's it for now! Please, if you have any doubts, don't hesitate to use the comments section, as I will keep an eye on it!


Original Link: https://dev.to/vicnovais/creating-a-deferred-promise-hook-in-react-39jh

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To