I am trying Nextjs parallel routes for my project, everything went well until I had to link it to a server action that deletes the post displayed in the modal an redirects to the home page.
using "next": "^14.2.1",
;
This is my file structure (As U can see I have added the default routes):
modal.tsx
:
"use client";
import { type ElementRef, useEffect, useRef } from "react";
import { useRouter } from "next/navigation";
import { createPortal } from "react-dom";
export function Modal({ children }: { children: React.ReactNode }) {
const router = useRouter();
const dialogRef = useRef<ElementRef<"dialog">>(null);
useEffect(() => {
if (!dialogRef.current?.open) {
dialogRef.current?.showModal();
}
}, []);
function onDismiss() {
router.back();
}
return createPortal(
<dialog
ref={dialogRef}
className="no-scrollbar m-0 h-screen w-screen bg-zinc-900/80 text-white"
onClose={onDismiss}
>
{children}
{/* <button onClick={onDismiss} className="close-button" /> */}
</dialog>,
document.getElementById("modal-root")!,
);
}
@modal/.(img)/[id]/page.tsx
:
import FullPageImaggeView from "~/components/full-image-page";
import { Modal } from "~/components/modal";
export default function PhotoModal({
params: { id: photoId },
}: {
params: { id: string };
}) {
return (
<div>
<Modal>
<FullPageImaggeView bgColorCode="900" photoId={photoId} />
</Modal>
</div>
);
}
full-image-page.tsx
:
import { getImage } from "~/server/queries";
import { Button } from "~/components/ui/button";
import clsx from "clsx";
import { removeImageAction } from "~/server/actions";
import { redirect } from "next/navigation";
export default async function FullPageImaggeView({
photoId,
bgColorCode,
}: {
photoId: string;
bgColorCode: string;
}) {
if (isNaN(Number(photoId))) throw new Error("Invalid Image id");
const image = await getImage(Number(photoId));
const removeImage = removeImageAction.bind(null, Number(photoId));
return (
<div className="no-scrollbar flex h-full w-screen min-w-0 flex-col items-center justify-center text-white">
<div
className={clsx(
{
"bg-neutral-900": bgColorCode === "900",
"bg-neutral-950": bgColorCode === "950",
},
`border-1.5 no-scrollbar m-4 max-h-screen max-w-screen-md border border-neutral-800`,
)}
>
<div className="flex-shrink flex-grow">
<img
src={image.url}
className="w-full object-contain md:max-h-[60vh]"
alt={image.name}
/>
</div>
<div className="flex w-full flex-shrink-0 flex-col border-neutral-800">
<div className="border-b border-neutral-800 p-2 text-center text-xl">
{image.name}
</div>
<div className="p-2">
<div>Uploaded By:</div>
<div className="overflow-clip">{image.userId}</div>
</div>
<div className="p-2">
<div>Created On:</div>
<div>{image.createdAt.toLocaleDateString()}</div>
</div>
<div className="p-2">
<form action={removeImage}>
<Button variant="destructive">Delete</Button>
</form>
<form action={async () => {
'use server';
redirect('/');
}}>
<Button variant="ghost">redirect</Button>
</form>
</div>
</div>
</div>
</div>
);
}
The server action is a simple drizzle delete query followed by Next Navigation's Redirect:
export const deleteImage = async (id: number) => {
const user = auth();
if (!user) throw new Error("Not Authorized");
const image = await db.query.posts.findFirst({
where: (model, { eq }) => eq(model.id, id),
});
if (!image) throw new Error('Image Not Found');
if (image.userId !== user.userId) throw new Error ('Not Authorized');
await db.delete(posts).where(and(eq(posts.id, id), eq(posts.userId, user.userId)));
};
The server action:
"use server";
import { deleteImage } from "../queries";
import { redirect } from "next/navigation";
export const removeImageAction = async (id: number) => {
await deleteImage(id);
redirect("/");
};
The issue at hand is that when the server action redirects to the homepage ('/'), the modal is not dismissed, which triggers an exception caused by the modal since it is trying to fetch an image that doesn't exist.
To try to understand, I even added a redirection button that redirects to the homepage without any other actions, this way no exceptions will be thrown. It was when I understood that the modal is not dismissed.
Note: userRouter.back()
dismisses the modal. but Redirect('/')
doesn't.
Here is a demo video to illustrate the situation better: https://www.loom.com/share/ac4ae48641c7465ab1aa21a2ec42bad8
Just an update here, I found the issue in my node_modules. I had to remove my modules and run npm install
, which solved it.