Next.js Discord

Discord Forum

Best Practice

Unanswered
Russian Blue posted this in #help-forum
Open in Discord
Russian BlueOP
I am in the current Page of an Item, there is a delete option to delete currentTask, but how can I do it so no errors come up that they were unable to fetch task with id = ".." since that task would have been deleted.

52 Replies

Russian BlueOP
Single Task Page:
import { getTaskById } from "@/services/taskServices";
import TaskEditIcon from "../_components/TaskEditIcon";
import TaskDeleteIcon from "../_components/TaskDeleteIcon";

const SingleTaskPage = async ({ params }: { params: { id: number } }) => {
  const task = await getTaskById(Number(params.id));
  if (!task) return;
  return (
    <div>
      <h1>Title: {task?.title}</h1>
      <h1>Description: {task?.description}</h1>
      <h1>
        Updated At:{" "}
        {task?.updatedAt?.toLocaleDateString() !== null
          ? task?.updatedAt?.toLocaleDateString()
          : task?.createdAt?.toLocaleDateString()}
      </h1>
      <TaskEditIcon task={task} />
      <TaskDeleteIcon taskId={params.id} />
    </div>
  );
};

export default SingleTaskPage;
TaskDeleteIcon:
"use client";
import { GiTrashCan } from "react-icons/gi";
import { deleteTask } from "../action";

import { toast } from "sonner";
import { useTransition } from "react";
import { usePathname, useRouter } from "next/navigation";
const TaskDeleteIcon = ({ taskId }: { taskId: number }) => {
  const [isPending, startTransition] = useTransition();
  const pathName = usePathname();
  const router = useRouter();
  const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    startTransition(() => {
      deleteTask(Number(taskId)).then((data) => {
        router.replace("/tasks");
        if (data?.error) {
          toast.error(data.error);
        }
        if (data?.success) {
          toast.success(data.success);
        }
      });
    });
  };
  return (
    <form onSubmit={onSubmit}>
      <button type="submit" disabled={isPending}>
        <GiTrashCan className="w-6 h-6 cursor-pointer hover:bg-red-500 p-[2px] rounded" />
      </button>
    </form>
  );
};

export default TaskDeleteIcon;
Action:
export const deleteTask = async (id: number) => {
  const user = await currentUser();
  const task = await db.task.findUnique({ where: { id } });
  if (user?.id != task?.creatorId)
    return { error: "You cannot delete tasks for others." };

  await db.task.delete({ where: { id } });
  revalidatePath("/tasks");
  return { success: "Task deleted successfully" };
};
@Russian Blue Action: export const deleteTask = async (id: number) => { const user = await currentUser(); const task = await db.task.findUnique({ where: { id } }); if (user?.id != task?.creatorId) return { error: "You cannot delete tasks for others." }; await db.task.delete({ where: { id } }); revalidatePath("/tasks"); return { success: "Task deleted successfully" }; };
export const deleteTask = async (id: number) => {
  const user = await currentUser();
  const task = await db.task.findUnique({ where: { id } });
  if (!task) {
    return { error: "Task doesn't exist" }
  }
  if (user?.id != task?.creatorId)
    return { error: "You cannot delete tasks for others." };

  await db.task.delete({ where: { id } });
  revalidatePath("/tasks");
  return { success: "Task deleted successfully" };
};
Russian BlueOP
error thrown in the console since it couldnt fetch a deleted task since it wouldn't exist after deletion:
getTaskById:
export const getTaskById = async (id: number) => {
  const user = await currentUser();
  try {
    const task = await db.task.findFirst({
      where: {
        id,
      },
    });
    if (task?.creatorId !== user?.id)
      throw new Error("You cannot fetch other people's tasks.");
    return task;
  } catch (error) {
    throw new Error("Failed to fetch task");
  }
};
Russian BlueOP
if you notice I tried rerouiting inside TaskDeleteIcon but I think there is more efficent way
findFirst return null if it is not exist
and you have this line
if (task?.creatorId !== user?.id)
      throw new Error("You cannot fetch other people's tasks.");
add this line if (!task) return null
@Ray findFirst return null if it is not exist
Russian BlueOP
I changed to findUnique
it also return null if not exist
Russian BlueOP
yep working now, what do you think of this, should I redirect in the delete function instead of in the TaskDeleteIcon?
@Ray add this line `if (!task) return null`
Russian BlueOP
do I do, if (!task) return redirect("/tasks")?
I don't think you need this
if the task is not exist, that button will not show right?
Russian BlueOP
right
the button is rendered as part of the task, every task has a button
yes so there is no need for redirect
Russian BlueOP
so I remove the router.replace insdie the TaskDeleteIcon?
Russian BlueOP
now if I delete, not found page shows up tho
what do you mean?
Russian BlueOP
this is the task page
@Russian Blue now if I delete, not found page shows up tho
oh I think you need redirect after the delete
Russian BlueOP
if i click delete, the task is no longer there, so the route is technically not found
export const deleteTask = async (id: number) => {
  const user = await currentUser();
  const task = await db.task.findUnique({ where: { id } });
  if (!task) {
    return { error: "Task doesn't exist" }
  }
  if (user?.id != task?.creatorId)
    return { error: "You cannot delete tasks for others." };

  await db.task.delete({ where: { id } });
  revalidatePath("/tasks");
  redirect('/tash');
};
"use client";
import { GiTrashCan } from "react-icons/gi";
import { deleteTask } from "../action";

import { toast } from "sonner";
import { useTransition } from "react";
import { usePathname, useRouter } from "next/navigation";
const TaskDeleteIcon = ({ taskId }: { taskId: number }) => {
  const [isPending, startTransition] = useTransition();
  const pathName = usePathname();
  const router = useRouter();
  const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    startTransition(() => {
      deleteTask(Number(taskId)).then((data) => {
        if (data?.error) {
          toast.error(data.error);
        }
        if (data?.success) {
          toast.success("Task deleted successfully");
        }
      });
    });
  };
  return (
    <form onSubmit={onSubmit}>
      <button type="submit" disabled={isPending}>
        <GiTrashCan className="w-6 h-6 cursor-pointer hover:bg-red-500 p-[2px] rounded" />
      </button>
    </form>
  );
};

export default TaskDeleteIcon;
@Ray oh I think you need redirect after the delete
Russian BlueOP
another thing that, on the tasks page, you can see all tasks, so you can delete there as well, does it also redirect or no since on the same path?
it will handle it in one request and response
Russian BlueOP
so no redundant redirects will take place, if I delete from the /tasks page
Russian BlueOP
if i redirect there, I cannot return success
I need the success message for my toaster
Russian BlueOP
I was thinking to add this in my TaskDeleteIcon:
const pathName = ...
if(pathName == `tasks/${taskId}`) router.replace("/tasks");
so it only redirects if the user was initially on the tasks/id page
its up to you but I would use redirect with server action
Russian BlueOP
and will only delete and not redirect if the user on the /tasks pafe
because router.replace() will make an extra request
you could check the network tab on browser dev tools
Russian BlueOP
oh okay
do I make my success message static for my toaster then?
Russian BlueOP
perfect, thanks mate.
@Russian Blue perfect, thanks mate.
no prob