javascriptreactjsnext.js

Parallel routes modal is not dismissed when redirecting to another page


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):

App structure

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


Solution

  • 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.