No More Messy Confirmation Code Inside Components.
Optimistic way of using the react query hooks to reduce the codes.

No More Messy Confirmation Code Inside Components.

Use react query hooks optimistically to reduce the confirmation code.

Introduction

Hooks are the main utility of the react query where we write the queries which cache the data and also the mutations to make the operations on the cached data or send request to the backend.

But imagine before deleting any sensitive data you want to show the confirmation to the user. The lines you see in every application "Are you sure you want to <operation>?". You see this in muliple websites, right? but let see how can we optimize the code for it using the react query hooks.

The Problem

// Before: Component bloated with confirmation + mutation logic
const TaskRow = ({ task }) => {
  const [open, setOpen] = useState(false);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const deleteItem = async () => {
    try {
      setLoading(true);
      await api.delete(`/task/${task.id}`);
      toast.success("Deleted!");
      refetchItems();
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
      setOpen(false);
    }
  };

  return (
    <>
      <button onClick={() => setOpen(true)}>Delete</button>

      {open && (
        <ConfirmModal
          loading={loading}
          error={error}
          onConfirm={deleteItem}
          onCancel={() => setOpen(false)}
        />
      )}
    </>
  );
};        

This is the one example of deletion but imagine you are developing a admin panel where you need to get lot of comfirmations / dielogues which are used at multiple areas of code sometime in single component we need two confirmations. How do we manage that? right. I was too have big question mark in my mind while I'm developing the admin panel where lots of confirmations and same dialogues for the multiple request to the backend some wanted to add some data in the payload before sending it to the backend.

Solution

The hook which gives us the asynchronous component.

// useConfirm.ts
import { useState, useCallback } from "react";
import {createPortal} from "react-dom";
export function useConfirm() {
  const [isOpen, setIsOpen] = useState(false);
  const [resolver, setResolver] = useState<(value: boolean) => void>(() => {});

  const ask = useCallback(() => {
    setIsOpen(true);
    return new Promise<boolean>((resolve) => {
      setResolver(() => resolve);
    });
  }, []);

  const onConfirm = () => {
    setIsOpen(false);
    resolver(true);
  };

  const onCancel = () => {
    setIsOpen(false);
    resolver(false);
  };

const DialogueUi = isOpen ? createPortal(
        <ConfirmModal
          loading={loading}
          error={error}
          onConfirm={onConfirm}
          onCancel={onCancle}
        />)

  return {
    ask,
    DialogueUi
  };
}        

Now the main react query hook we have

export function useDeleteTask(options) { 
  const mutation = useMutation(deleteTaskQuery, options);
  const { ask, DialogueUi } = useConfirm();

  const mutateWithConfirm = async (variables) => {
    // a confirmation modal will be shown here
    const ok = await ask(variables);
    // if you click on the cancel the request will go back from here only
    if (!ok) return;
    // here you can get the 
    mutation.mutate(variables);
  };
   
  return { ...mutation, mutateWithConfirm, DialogueUi};
}        

And this useDeleteTask can be used to delete the data which you can see it in below code.

const { mutateWithConfirm, isLoading, DialogueUi } = useDeleteTask({
  onSuccess: () => {
    toast.success("Deleted!");
    queryClient.invalidateQueries(["tasks"]);
  },
});

const handleDelete = () => {
  mutateWithConfirm({ id: task.id, title: task.title });
};

return (
  <>
    <button disabled={isLoading} onClick={handleDelete}>
      Delete Task
    </button>
    {DialogueUi}
  </>
);        

For updating the payload

Some times we have the dialogues which are used to add the data into the payload before sending them to the backend the in this case how you can we get that data let see

  // the modal will return additional data selected by the user
  const onConfirm = (data) => {
    setIsOpen(false);
    // this resolver send the data back to the react query hook
    resolver({confirm:true,data:{..data}});
  };        

Above data we took inside the react hook and send it to the backend

 const mutateWithConfirm = async (variables) => {
    // showing the modal to the user by taking the success and data from it
    const {success, data = {}} = await ask();
    // if you click on the cancel the request will go back from here only
    if (!success)return;
    // here you that data will be appended inside the payload
    mutation.mutate({...variables,...data});
  };        

Conclusion

Using this architecture, the component no longer carries the burden of confirmation logic or modal states. The React Query mutation remains responsible only for data changes, while the confirmation UI lives cleanly within the few components where it’s actually required.

To view or add a comment, sign in

More articles by Siddhant Kadam

Others also viewed

Explore content categories