任何人都可以向我解释或帮助解决我的代码中面临的问题。我正在尝试从 NodeJS 的后端获取数据。并在我的 nextjs 前端应用程序中渲染它,我收到此错误:“错误:比之前的渲染期间渲染了更多的钩子。nextjs 和 shadcn 数据表”
这是我的组件:
"use client"
import React ,{useState, useEffect} from "react"
import {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table"
import { ArrowUpDown, ChevronDown, MoreHorizontal } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Input } from "@/components/ui/input"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { useQuery } from 'react-query'
// const data: Payment[] = [
// {
// id: "m5gr84i9",
// phone: "316",
// status: "success",
// name : "ken99",
// admin_email: "[email protected]",
// banned : false
// },
// {
// id: "3u1reuv4",
// phone: "242",
// status: "success",
// name : "test",
// admin_email: "[email protected]",
// banned : false
// },
// {
// id: "derv1ws0",
// phone: "837",
// status: "processing",
// name : "test",
// admin_email: "[email protected]",
// banned : false
// },
// {
// id: "5kma53ae",
// phone: "874",
// status: "success",
// name : "test",
// admin_email: "[email protected]",
// banned : false
// },
// {
// id: "bhqecj4p",
// phone: "721",
// status: "failed",
// name : "test",
// admin_email: "[email protected]",
// banned : false
// },
// ]
// export type Payment = {
// id: string,
// name: string,
// admin_email: string,
// phone: string,
// status: string,
// banned: boolean
// }
export const columns: any = [
{
id: "select",
header: ({ table } : any) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row } : any) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "status",
header: "Status",
cell: ({ row } : any) => (
<div className="capitalize">{row.getValue("status")}</div>
),
},
{
accessorKey: "name",
header: ({ column } : any) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Name
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
)
},
cell: ({ row } : any) => <div className="lowercase">{row.getValue("name")}</div>,
},
{
accessorKey: "admin_email",
header: ({ column } : any) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Email
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
)
},
cell: ({ row } : any) => <div className="lowercase">{row.getValue("admin_email")}</div>,
},
{
accessorKey: "phone",
header: () => <div className="text-right">Phone</div>,
cell: ({ row } : any) => {
const phone = parseFloat(row.getValue("phone"))
return <div className="text-right font-medium">{phone}</div>
},
},
{
id: "actions",
enableHiding: false,
cell: ({ row } : any) => {
const payment = row.original
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">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(payment.id)}
>
Copy payment ID
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>View customer</DropdownMenuItem>
<DropdownMenuItem>View payment details</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
},
},
]
export function SchoolTable() {
const [sorting, setSorting] = useState<SortingState>([])
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>(
[]
)
const [columnVisibility, setColumnVisibility] =
useState<VisibilityState>({})
const [rowSelection, setRowSelection] = useState({})
const { isLoading, error, data } : any = useQuery('repoData', () =>
fetch('http://localhost:5000/api/getSchools?page=1').then(res =>
res.json()
)
)
if (isLoading) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
const table = useReactTable({
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
})
return (
<div className="w-full">
<div className="flex items-center py-4">
<Input
placeholder="Filter emails..."
value={(table.getColumn("email")?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn("email")?.setFilterValue(event.target.value)
}
className="max-w-sm"
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto">
Columns <ChevronDown className="ml-2 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
)
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<div className="space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
</div>
)
}
这是我在 Nodejs 中的后端:
exports.getSchools = async (req, res) => {
try {
const page = req.query.page || 1;
const limit = req.query.limit || 10;
const skip = (page - 1) * limit;
const schools = await School.find()
.skip(skip)
.limit(limit)
.select('id logo name admin_email phone plan status banned');
const totalCount = await School.countDocuments();
const totalPages = Math.ceil(totalCount / limit);
const pagination = {
currentPage: page,
totalPages: totalPages,
totalCount: totalCount,
};
res.status(200).json({ schools, pagination });
} catch (err) {
console.error(err);
res.status(500).json({ message: 'Internal server error', error: err });
}
};
从中获取数据的路线是http://localhost:5000/api/getSchools?page=1
React 依赖于钩子的顺序/数量(对于您的情况来说只是以
use
开头的函数)以便正确管理和更新状态。
因此,您必须在任何条件语句之前调用顶层的所有钩子,以防止出现意外的错误。按如下方式重新排序您的代码:
const table = useReactTable({
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
})
if (isLoading) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message