#Dialog rendering outside

1 messages · Page 1 of 1 (latest)

mental gorge
#

"use client";

import { ColumnDef } from "@tanstack/react-table";
import { Ellipsis, MoveDown, MoveUp, Pencil, Trash2 } from "lucide-react";
import { useState } from "react";
import { Button } from "~/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "~/components/ui/dropdown-menu";
import { formatDate } from "~/lib/date-stuffs";
import { DeleteStudentForm } from "./actions-stuff/delete-student-form";

export type Student = {
lrn: string;
fullName: string;
sex: string;
createdAt: Date;
};

function StudentActionsDropdown({ lrn, fullName, sex, createdAt }: Student) {
const [isDeleteOpen, setIsDeleteOpen] = useState(false);

return (
<>

  <DropdownMenu>
    <DropdownMenuTrigger asChild>
      <Button variant="ghost" className="text-ri h-8 w-8 p-0">
        <span className="sr-only">Open actions</span>
        <Ellipsis className="h-4 w-4" />
      </Button>
    </DropdownMenuTrigger>
    <DropdownMenuContent align="end">
      <DropdownMenuLabel>Actions</DropdownMenuLabel>
      <DropdownMenuSeparator />
      <DropdownMenuItem className="hover:cursor-pointer">
        <Pencil className="mr-2 h-4 w-4" />
        <span>Edit student info</span>
      </DropdownMenuItem>
      <DropdownMenuItem
        onClick={() => setIsDeleteOpen(true)} // Change here to open the dialog
        className="text-red-600 hover:cursor-pointer focus:text-red-600"
      >
        <Trash2 className="mr-2 h-4 w-4" />
        <span>Delete student</span>
      </DropdownMenuItem>
    </DropdownMenuContent>
  </DropdownMenu>
</>

);
}

export const columns: ColumnDef<Student>[] = [
{
id: "actions",
enableHiding: false,
cell: ({ row }) => {
const val = row.original;

  return <StudentActionsDropdown {...val} />;
},

},
];

haughty latchBOT
#

Hi @mental gorge. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Based on the provided code snippet and the knowledge sources, I can see that you're encountering an issue with rendering a dialog outside of a Client Component in Next.js. This is a common challenge when working with the App Router in Next.js. Let me explain the problem and provide a solution.

The issue arises because you're trying to render a dialog (presumably the DeleteStudentForm component) outside of the "use client" boundary. In Next.js with the App Router, components are Server Components by default, and client-side interactivity needs to be explicitly defined.

To solve this, you need to move the client-side logic, including the state management for the dialog, into a separate Client Component. Here's how you can modify your code:

  1. Create a new Client Component for the actions dropdown:
'use client';

import { useState } from "react";
import { Button } from "~/components/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "~/components/ui/dropdown-menu";
import { Ellipsis, Pencil, Trash2 } from "lucide-react";
import { DeleteStudentForm } from "./actions-stuff/delete-student-form";

export function StudentActionsDropdown({ lrn, fullName, sex, createdAt }: Student) {
  const [isDeleteOpen, setIsDeleteOpen] = useState(false);

  return (
    <>
      <DropdownMenu>
        {/* ... (your existing dropdown code) ... */}
      </DropdownMenu>
      {isDeleteOpen && (
        <DeleteStudentForm
          lrn={lrn}
          fullName={fullName}
          onClose={() => setIsDeleteOpen(false)}
        />
      )}
    </>
  );
}
  1. Update your main component to use this new Client Component:
#
import { ColumnDef } from "@tanstack/react-table";
import { StudentActionsDropdown } from "./StudentActionsDropdown";

export type Student = {
  lrn: string;
  fullName: string;
  sex: string;
  createdAt: Date;
};

export const columns: ColumnDef<Student>[] = [
  {
    id: "actions",
    enableHiding: false,
    cell: ({ row }) => {
      const val = row.original;
      return <StudentActionsDropdown {...val} />;
    },
  },
];

By separating the client-side logic into its own component with the 'use client' directive, you ensure that the dialog rendering and state management occur within the client-side boundary. This approach follows the recommended pattern for interleaving Server and Client Components in Next.js as described in the Next.js documentation (https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#interleaving-server-and-client-components).

Remember that when using this pattern, you're creating a client-side boundary, which means that all components imported into the StudentActionsDropdown component will also be part of the client bundle. Be mindful of this when optimizing for performance.

This solution should resolve the issue of rendering the dialog outside the client boundary while maintaining the functionality of your dropdown menu and delete action.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: