Sorting — সাজানো
sorting state, header-এর toggle, multi-sort, custom sortingFn, sortUndefined, যাচাই করা ও সাধারণ বাগ — বাংলায়।
সাজানো হলো table-এর সবচেয়ে বেশি ব্যবহৃত feature। মূল ব্যাপারটা খুব সহজ — একটা sorting state, একটা row model, আর header-এ একটা button। বাকি সব detail।
তিন লাইনের সেটআপ
"use client";
import { useState } from "react";
import {
getCoreRowModel,
getSortedRowModel,
useReactTable,
type SortingState,
} from "@tanstack/react-table";
export function DataTable({ data, columns }: DataTableProps) {
const [sorting, setSorting] = useState<SortingState>([]);
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
state: { sorting }, // ← এই অবস্থা থেকেই সব হবে
onSortingChange: setSorting, // ← এখানেই state বদলাবে
});
// … rendering
}sorting state-এর আকার একটা ছোট object-এর array:
type SortDirection = false | "asc" | "desc";
type SortingState = { id: string; desc: boolean }[];
// মানে কী
[] // সাজানো নেই
[{ id: "price", desc: true }] // মূল্য বড় থেকে ছোট
[{ id: "name", desc: false }] // নাম অনুক্রমেconst [sorting, setSorting] = useState<SortingState>([
{ id: "price", desc: true },
]);header-এ সাজানোর button
function SortableHeader({
column,
children,
}: {
column: Column<Product>;
children: React.ReactNode;
}) {
const direction = column.getIsSorted();
return (
<Button
variant="ghost"
className="-ml-3 h-8"
onClick={() => column.toggleSorting(direction === "asc")}
>
{children}
{direction === "asc" && <ArrowUp className="ml-2 size-4" />}
{direction === "desc" && <ArrowDown className="ml-2 size-4" />}
{!direction && <ChevronsUpDown className="ml-2 size-4 opacity-50" />}
</Button>
);
}
// columns-এ ব্যবহার
{
accessorKey: "price",
header: ({ column }) => (
<SortableHeader column={column}>মূল্য</SortableHeader>
),
}toggleSorting(true) = asc হবে, toggleSorting(false) = desc হবে। false-এর অর্থ "আর সাজাও না"।
getIsSorted-এর তিন অবস্থা
const direction = column.getIsSorted();
// direction === false → এখনো সাজানো হয়নি
// direction === "asc" → ছোট থেকে বড়
// direction === "desc" → বড় থেকে ছোট
// বিকল্প — দুটো API
column.getSortDirection(); // "asc" | "desc" | false
column.getIsSorted(); // একই জিনিস
// isSorted বলে এখন এটাই active কিনা
column.getIsSorting(); // boolean// তিন ধাপের cycle: none → asc → desc → none
onClick={() => column.toggleSorting(
column.getIsSorted() === "asc",
)}
// শুধু asc/desc, কখনো none নয় — বার বার ক্লিক করলেও কাজ করে
onClick={() => column.toggleSorting(
column.getIsSorted() === "asc",
false, // enableThirdState: false
)}multi-sort — shift + ক্লিক
বড় টেবিলে সাধারণত একবারে একটাই কলাম সাজায়। কিন্তু মাঝে মাঝে দরকার হয় "এই ক্যাটাগরির ভেতরে মূল্য দেখাও"। ডিফল্টে shift ধরে ক্লিক করলেই multi-sort হয়।
// enabled: true হলে shift-click দিয়ে একাধিক কলাম জোড়া হবে
const [sorting, setSorting] = useState<SortingState>([
{ id: "category", desc: false },
{ id: "price", desc: true },
]);
// ফলাফল: category অনুক্রমে, তার ভেতরে বড় মূল্য আগেmulti-sort-এর অগ্রাধিকার হলো array-এর ক্রম — [0] সবচেয়ে গুরুত্বপূর্ণ। তাই priority বদলাতে অ্যারে সাজিয়ে দিন।
setSorting([
{ id: "category", desc: false }, // ১ম অগ্রাধিকার
{ id: "price", desc: true }, // ২য় অগ্রাধিকার
]);যাচাই করার উপায়
// যে কলামে ক্লিক করলাম, সেটাই কী সাজানো হচ্ছে
const first = table.getState().sorting[0];
console.log(first?.id, first?.desc ? "desc" : "asc");
// নির্দিষ্ট কলামের অবস্থা
table.getColumn("price").getIsSorted(); // "desc"
// সব sorting মুছে ফেলা
table.resetSorting();
table.setSorting([]);
// ঐচ্ছিক কলাম বাদ দিয়ে সব ফেরত
table.toggleAllColumnsSorted(true); // সব asc
table.toggleAllColumnsSorted(false); // সব descকোন কলাম sortable নয়
{
accessorKey: "name",
header: "নাম", // ← এখানে কোনো button নেই
enableSorting: false,
}
{
id: "actions",
enableSorting: false, // ← অ্যাকশন কলাম
}
{
accessorKey: "createdAt",
sortingFn: "datetime", // নিজস্ব comparator
}enableSorting: false দিলে getSortedRowModel() ওই কলামটা উপেক্ষা করে — header-এ button না দিয়ে state-এও ঢোকানো যায় না।
Custom sortingFn
TanStack ডিফল্ট হিসেবে কয়েকটা comparator দেয়। কিন্তু বাস্তবে বেশিরভাগ ক্ষেত্রে একটা-দুইটা custom লাগেই — বিশেষ করে বাংলা টেক্সট ও নাম সাজানোর জন্য।
import {
type SortingFn,
type ColumnDef,
} from "@tanstack/react-table";
// রাশির কম্প্যারিশন — আগে/পরে না দেখে
const priceCompare: SortingFn<number> = (a, b, rowA, rowB) =>
(a ?? 0) - (b ?? 0);
const columns: ColumnDef<Product>[] = [
{
accessorKey: "price",
sortingFn: priceCompare,
},
// বাংলা ও ইংরেজি মিলিয়ে সাজানো
{
accessorKey: "name",
sortingFn: (a, b, rowA) =>
a.localeCompare(b, "bn-BD", { numeric: true, sensitivity: "base" }),
},
// stock: কম আগে? বেশি আগে?
{
accessorKey: "stock",
sortingFn: (a, b) => (a ?? 0) - (b ?? 0),
},
];import type { SortingFn } from "@tanstack/react-table";
const dateCompare: SortingFn<string> = (a, b) =>
new Date(a).getTime() - new Date(b).getTime();localeCompare(b, "bn-BD") ব্যবহার করুন, a > b নয়। সাধারণ > দিয়ে সাজালে "আ" < "ক" < "খ" ভুল ক্রমে আসে, আর "10" < "9" হয়ে যায় — কারণ string তুলনা হচ্ছে। localeCompare এগুলো ঠিক করে, সাথে numeric: true দিলে সংখ্যাও ঠিকমতো সাজায়।
sortUndefined — ফাঁকা মান কোথায় যাবে
// null/undefined সবসময় শেষে রাখতে (default)
useReactTable({ sortUndefined: "last" })
// সবসময় প্রথমে
useReactTable({ sortUndefined: "first" })
// অ্যাসাই-সহ ফাঁকা মান আলাদা করে
useReactTable({ sortUndefined: "last" })এটা বেশিরভাগ ডেটাবেসেই দরকার — rating null হলে সেটা প্রথমে চলে গেলে মনে হয় সবচেয়ে খারাপ item টপে।
URL-এ sorting রাখা
controlled state-এর সবচেয়ে বড় সুবিধা — state যেখানে খুশি রাখা যায়, URL-এও।
"use client";
import { useSearchParams } from "next/navigation";
import { useState } from "react";
export function DataTable({ data, columns }: DataTableProps) {
const searchParams = useSearchParams();
const sortParam = searchParams.get("sort"); // "price:desc"
const [sorting, setSorting] = useState<SortingState>(() =>
sortParam
? [{
id: sortParam.split(":")[0],
desc: sortParam.split(":")[1] === "desc",
}]
: [],
);
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
state: { sorting },
onSortingChange: (updater) => {
setSorting(updater);
// এখানে URL update করুন
},
});
}onSortingChange একটা function পায় (updater) — state আগের মান হলে সেটা, নতুন হলে সেটা। তাই সরাসরি setSorting(updater) লেখা ঠিক, onSortingChange: setSorting-ও কাজ করে।
সাধারণ বাগ
| ঘটনা | কারণ | সমাধান |
|---|---|---|
| ক্লিক করলেও সাজায় না | getSortedRowModel() দেওয়া হয়নি | row model যোগ করুন |
| state বদলায়, কিন্তু UI নড়ে না | state option দেওয়া হয়নি | state: { sorting } দিন |
| বাংলা নাম ভুল ক্রমে | > ব্যবহার করেছেন | localeCompare("bn-BD") |
| "১০" আগে "৯" | string তুলনা | numeric: true |
| নতুন কলাম click করলে পুরোনোটা থাকে | multi-sort চালু | shift ছাড়া click করতে হবে |
id মিসম্যাচ | কলাম id বদলেছেন বা id দেননি | sorting[0].id ডিবাগে দেখুন |
| sorting মুছে যায় | columns নতুন অ্যারে প্রতি রেন্ডারে | columns বাইরে সরাসরি রাখুন / useMemo |
useReactTable-এর ভেতরে const columns = [...] লিখলে প্রতি রেন্ডারে নতুন অ্যারে বানে, table পুরো হিসাব আবার করে এবং state চলে যেতে পারে। columns ফাইলের উপরে export const columns হিসেবে রাখুন অথবা prop হিসেবে parent থেকে পাঠান।
সারসংক্ষেপ
sorting: { id, desc }[]+getSortedRowModel()— এটাই সবcolumn.toggleSorting()দিয়ে header-এর cycle- shift+click = multi-sort, অ্যারের ক্রমই priority
- বাংলা ও সংখ্যা সাজাতে
localeCompare—>নয় sortUndefinedদিয়ে null ঠিক জায়গায় রাখুন
পরবর্তী
সাজানো শেষ। পরের অধ্যায়ে filtering — global search ও column filter, built-in filterFns, আর filter বদলালে সব ভুল যে জায়গায় ঘাটা খেয়ে যায়।