shadcn-ui / taxonomy

An open source application built using the new router, server components and everything new in Next.js 13.
https://tx.shadcn.com
MIT License
18.63k stars 2.58k forks source link

Suitable Way of Displaying Toast from Data Table Row Action #293

Closed jadejamig closed 6 months ago

jadejamig commented 6 months ago

I have a delete action in my data table row action, I wanted to display a toast for a successful deletion. I am able to get the expected result in dev run, but I am getting this ESLint rule error upon building the project for production.

Error: React Hook "useToast" is called in function "cell" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".

Based on next.js docs, it's possible to disable rules, but is there a more suitable way of going around this?

Here is my columns.tsx file:

"use client";

export const columns: ColumnDef<File>[] = [
  {
    ...
  },
  {
    id: "actions",
    cell: ({ row }) => {
      const file = row.original;

      const { toast } = useToast();
      const onDeleteFile = async () => {

          const response = await deleteFileById(file.id);

          if (response.success) {
            toast({
              duration: 2000,
              variant: "success",
              description: response.success
          })
          }
      };

      return (
        <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <Button variant="ghost" className="h-8 w-8 p-0">
              <span className="sr-only">Open menu</span>
              <MoreHorizontal className="h-4 w-4" />
            </Button>
          </DropdownMenuTrigger>
          <DropdownMenuContent align="end">
            <DropdownMenuItem>
                <Button 
                  variant='destructive' 
                  className="flex gap-x-2 w-full"
                  onClick={ async () => await onDeleteFile()} 
                >
                    <Trash className="h-4 w-4"/>
                    Delete File
                </Button>
            </DropdownMenuItem>
          </DropdownMenuContent>
        </DropdownMenu>
      )
    },
  }
]
jadejamig commented 6 months ago

Solved this by creating a separate cell action component:

const CellAction = ({ row }: CellContext<File, unknown>) => {
  const file = row.original;
  const { toast } = useToast();
  const router = useRouter();

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="ghost" className="h-8 w-8 p-0">
          <span className="sr-only">Open menu</span>
          <MoreHorizontal className="h-4 w-4" />
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuItem 
          className='text-red-600 focus:text-red-700 bg-red-100/50'
          onClick={ async () => {
            const { success } = await deleteFileById(file.key)

                  if (!success) {
                    toast({
                      duration: 4000,
                      variant: "default",
                      description: `💥 Something went wrong, couldn't delete the file!`
                    })
                    return
                  }

                  toast({
                    duration: 4000,
                    variant: "success",
                    description: `🗑 Deleted successfully!`
                  })

                  router.refresh();
          }}
        >
            <Trash className="mr-2 h-4 w-4" />
            <span>Delete file</span>
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

And passing it in the column definition:


export const columns: ColumnDef<File>[] = [
  {
   ...
  },
  {
    id: "actions",
    cell: CellAction,
  }
]