I am building a nextjs application where i have multiple tabs , table to display data etc. whenever the table code is uncommented the freezing of the code occurs which stops only when i close the tab. tested on browsers: chrome and edge.
i have a very identical code which works absolutely correct.
here is the code
"use client";
import React, { useState, useMemo, Suspense, useCallback, useEffect } from "react";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { Tabs, TabsList, TabsTrigger } from "~/components/ui/tabs";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "~/components/ui/table";
import {
ColumnFiltersState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import {
Eye,
Download,
Pencil,
Trash2,
Plus,
Search,
ArrowUpDown,
Import,
} from "lucide-react";
import { Module } from "./types";
import { columns } from "./columns";
import UpdateModuleCard from "./update-module-card";
import QuestionDrawer from "./questionDrawer";
import { useRouter } from "next/navigation";
import { api } from "~/trpc/react";
import CreateModuleCard from "~/components/content/modules/module/questions/CreateModule";
export default function ModulesPage({
params,
}: {
params: {
moduleId: string;
levelId: string;
courseId: string;
country: string;
};
}) {
const [sorting, setSorting] = useState<SortingState>([]);
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
const [rowSelection, setRowSelection] = useState({});
const [searchQuery, setSearchQuery] = useState("");
const [showUpdateModule, setShowUpdateModule] = useState(false);
const [selectedModule, setSelectedModule] = useState<Module | null>(null);
const [selectedTab, setSelectedTab] = useState("questions");
const [createModule, setCreateModule] = useState(false);
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [language, setLanguage] = useState("swedish");
const {
data: questions,
isLoading,
isError,
} = api.content.fetchLevel.useQuery({
id: params.levelId,
language: language,
type: "questions",
});
const router = useRouter();
useEffect(() => {
console.log(params, "params");
console.log(language, "language");
}, []);
const table = useReactTable({
data: questions?.questions || [],
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
});
const handleTabChange = useCallback((tab: string) => {
setSelectedTab(tab);
if (tab === "questions") {
router.push(
`/content/country/${params.country}/courses/${params.courseId}/modules/module/${params.moduleId}/level/${params.levelId}/questions`,
);
} else if (tab === "theory") {
router.push(
`/content/country/${params.country}/courses/${params.courseId}/modules/module/${params.moduleId}/level/${params.levelId}/theory`,
);
}
}, [params, router]);
const handleLanguageChange = (value: string) => {
setLanguage(value as 'swedish' | 'english')
console.log(value, "value");
};
const handleSearch = (event: React.ChangeEvent<HTMLInputElement>)=>{
const value = event.target.value
setSearchQuery(value)
table.getColumn('name')?.setFilterValue(value)
}
const handleEdit = () => {
const selectedRows = table.getFilteredSelectedRowModel().rows
if (selectedRows.length === 1) {
setSelectedModule(selectedRows[0].original)
setShowUpdateModule(true)
}
}
const handleUpdateModule = (e: React.FormEvent) => {
e.preventDefault()
// Handle module update logic here
setShowUpdateModule(false)
}
const handleRowClick = (row: Module) => {
setSelectedModule(row)
setIsDrawerOpen(true)
}
if (isLoading) {
return <div>Loading...</div>;
}
if (isError) {
return <div>Error</div>;
}
return (
<>
{showUpdateModule && selectedModule ? (
<UpdateModuleCard
module={selectedModule}
onClose={() => setShowUpdateModule(false)}
/>
) : (
<div className="flex p-8">
<div
className={`flex-grow transition-all duration-300 ${isDrawerOpen ? "pr-96" : ""}`}
>
<div className="mb-6 flex items-center justify-between">
<h1 className="text-2xl font-bold">
{selectedTab === "questions" ? "Questions" : "Theory"}
</h1>
<div className="flex space-x-2">
<Button onClick={()=>{setCreateModule(true); }}>
<Plus className="mr-2 h-4 w-4" /> Create{" "} Question
{/* {selectedTab === "questions" ? "Question" : "Theory"} */}
</Button>
<Button variant="outline">
<Import className="mr-2 h-4 w-4" />
Bulk Import
</Button>
</div>
</div>
<Tabs
value={selectedTab}
onValueChange={handleTabChange}
className="mb-6"
>
<TabsList>
<TabsTrigger value="theory">Theory</TabsTrigger>
<TabsTrigger value="questions">Questions</TabsTrigger>
</TabsList>
</Tabs>
<div className="mb-4 flex items-center justify-between">
<div className="relative flex items-center">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-gray-500" />
<Input
placeholder="Search..."
value={searchQuery}
// onChange={handleSearch}
className="pl-8 pr-8"
/>
<Button variant="outline" size="icon" className="ml-2">
<ArrowUpDown className="h-4 w-4" />
</Button>
</div>
<div className="flex items-center space-x-2">
<Button variant="outline" size="icon">
<Eye className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon">
<Download className="h-4 w-4" />
</Button>
</div>
</div>
<div className="mb-6 flex space-x-2">
<Tabs
defaultValue={"swedish"}
value={language}
onValueChange={handleLanguageChange}
className="mx-1 w-full"
>
<TabsList>
<TabsTrigger value="swedish">Swedish</TabsTrigger>
<TabsTrigger value="english">English</TabsTrigger>
</TabsList>
</Tabs>
</div>
{Object.keys(rowSelection).length > 0 && (
<div className="mb-4 flex space-x-2">
<Button variant="outline" size="sm">
// onClick={handleEdit}
>
<Pencil className="mr-2 h-4 w-4" /> Edit
</Button>
<Button
variant="outline"
size="sm"
className="text-red-500 hover:text-red-700"
>
<Trash2 className="mr-2 h-4 w-4" /> Delete
</Button>
</div>
)}
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) =>
<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"}
onClick={() => handleRowClick(row.original)}
>
{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-between 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>
{ (isDrawerOpen && selectedModule) &&
<QuestionDrawer
isOpen={isDrawerOpen}
onClose={() => setIsDrawerOpen(false)}
question={selectedModule}
/>
}
</div>
)}
{ createModule &&
// <Suspense fallback={<div>Loading...</div>}>
<CreateModuleCard
open={createModule}
setOpen={setCreateModule}
levelId={params.levelId}
/>
// </Suspense>
)}
</>
);
}
here is creating components
"use client";
import React, { useEffect, useState } from "react";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { Label } from "~/components/ui/label";
import { Switch } from "~/components/ui/switch";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "~/components/ui/select";
import { Dialog, DialogContent, DialogFooter } from "~/components/ui/dialog";
import Image from "next/image";
import { api } from "~/trpc/react";
import Tiptap from "~/components/global/tiptap";
import { ScrollArea } from "~/components/ui/scroll-area";
export default function CreateModuleCard({
open,
setOpen,
levelId,
}: {
open: boolean;
setOpen: (open: boolean) => void;
levelId: string;
}) {
const [newItemData, setNewItemData] = useState<Record<string, any>>({});
const [options, setOptions] = useState<{ text: string; isCorrect: boolean }[]>(
[],
);
const [newOption, setNewOption] = useState<string>("");
const {
data: languages,
isLoading,
isError,
} = api.global.fetchAllLanguages.useQuery();
const createLevelQuestion = api.content.createLevelQuestion.useMutation();
// useEffect(() => {
// if (open && module) {
// console.log("Module:", module);
// setNewItemData(module);
// }
// }, [open, module]);
const handleAddOption = (event: React.FormEvent) => {
event.preventDefault();
if (newOption.trim() !== "") {
setOptions([...options, { text: newOption, isCorrect: false }]);
setNewOption("");
}
};
const handleDeleteOption = (index: number) => {
setOptions(options.filter((_, i) => i !== index));
};
const handleCorrectToggle = (index: number) => {
setOptions((prevOptions) =>
prevOptions.map((option, i) =>
i === index ? { ...option, isCorrect: !option.isCorrect } : option,
),
);
};
const handleInputChange = (
key: string,
value: string | File | null | boolean | string[],
) => {
console.log(newItemData, "newItemData");
setNewItemData((prev) => ({ ...prev, [key]: value }));
};
const onUpdate = async () => {
console.log(newItemData);
await createLevelQuestion.mutateAsync({
levelId: levelId,
languageId: newItemData.language,
explanation: newItemData.content,
question: newItemData.question,
options: options,
isHidden: newItemData.isHidden,
});
setOpen(false);
};
if (isError) return <div>Error</div>;
return (
<Dialog open={open} onOpenChange={setOpen} modal={false}>
<DialogContent className="flex flex-col sm:max-w-[500px] max-h-[700px]" >
<ScrollArea className="flex-grow overflow-auto">
<form className="space-y-4">
<div className="flex space-x-4">
<div className="flex-1">
<Label htmlFor="thumbnail">Thumbnail</Label>
<div className="mt-1 flex items-center">
{newItemData.thumbnail && (
<Image
width={32}
height={32}
src={newItemData.thumbnail!}
alt="Thumbnail"
className="mr-4 h-16 w-16 rounded"
/>
)}
<Label
htmlFor="file-upload"
className="cursor-pointer rounded-md bg-white font-medium text-blue-600 hover:text-blue-500"
>
<span>Upload a file</span>
<Input
id="file-upload"
name="file-upload"
type="file"
className="sr-only"
onChange={(e) =>
handleInputChange(
"thumbnail",
e.target.files?.item.name![0],
)
}
/>
</Label>
</div>
</div>
</div>
{/* Dynamic Titles, Slug, and Language */}
<div className="flex-1">
<Label htmlFor="slug">Slug</Label>
<Input
id="slug"
value={newItemData.slug}
onChange={(e) => handleInputChange("slug", e.target.value)}
/>
</div>
<div className="flex-1">
<Label htmlFor="language">Language</Label>
{isLoading ? (
<div>Loading...</div>
) : (
<Select
value={newItemData.language?.id} // Change to id
onValueChange={(value) =>
handleInputChange("language", value)
} // value will be the id now
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select a Language" />
</SelectTrigger>
<SelectContent>
{languages!.map((lang) => (
<SelectItem key={lang.id} value={lang.id}>
{" "}
{/* Store id instead of name */}
{lang.name!}
</SelectItem>
))}
</SelectContent>
</Select>
)}
</div>
<div className="flex-1">
<Label htmlFor="language">Question</Label>
<Tiptap
className="mt-3 h-[100px]"
message={""}
handleMessageChange={(message) =>
handleInputChange("question", message)
}
isLoadingMessage={false}
hotReload={false}
/>
</div>
<div className="flex-1">
<div className="mt-4">
<Label htmlFor="options">Options</Label>
<div className="mt-2 flex space-x-2">
<Input
value={newOption}
onChange={(e) => setNewOption(e.target.value)}
placeholder="Add new option"
/>
<Button type="button" onClick={handleAddOption}>
Add Option
</Button>
</div>
<ul className="mt-4 space-y-2">
{options.map((option, index) => (
<li key={index} className="flex items-center space-x-2">
<Input
value={option.text}
onChange={(e) =>
setOptions((prevOptions) =>
prevOptions.map((opt, i) =>
i == index
? { ...opt, text: e.target.value }
: opt,
),
)
}
/>
<label class="flex items-center">
<input
type="checkbox"
checked={option.isCorrect}
onChange={() => handleCorrectToggle(index)}
/>
<span className="ml-2">Correct</span>
</label>
<Button
variant="outline"
size="sm"
onClick={() => handleDeleteOption(index)}
>
Delete
</Button>
</li>
))}
</ul>
</div>
<Label htmlFor="language">Answer Description</Label>
<Tiptap
className="mt-3 h-[250px]"
message={""}
handleMessageChange={(message) =>
handleInputChange("content", message)
}
isLoadingMessage={false}
hotReload={false}
/>
</div>
{/* Additional form inputs */}
<div className="mt-4 flex items-center space-x-2">
<Switch
id="is-hidden"
defaultChecked={false}
checked={newItemData.isHidden}
onCheckedChange={(checked) => {
handleInputChange("isHidden", checked);
}}
/>
<Label htmlFor="is-hidden">Is Hidden</Label>
</div>
<div className="mt-4 flex items-center space-x-2">
<Switch
id="is-practice"
defaultChecked={false}
checked={newItemData.isPractice}
onCheckedChange={(checked) => {
handleInputChange("isPractice", checked);
}}
/>
<Label htmlFor="is-practice">Is Practice</Label>
</div>
</form>
</ScrollArea>
<DialogFooter>
<Button type="submit" onClick={()=>{onUpdate()}}>
Update
</Button>
<Button variant="outline" onClick={() => setOpen(false)}>
Cancel
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}