Tech TutorBangla Web Documentation
Portfolio
HomeHTMLCSSTailwind CSSJavaScriptTypeScriptReactNext.jsNode.jsExpressSocket.IOExpoWordPressPostgreSQLPrismaPostgresNestJSMongoDBDockerTanStackTanStack TableRTK QueryGitGitHub
  • TanStack Table Documentation12টি অধ্যায়
প্রযুক্তি নির্বাচন
HomeHTMLCSSTailwind CSSJavaScriptTypeScriptReactNext.jsNode.jsExpressSocket.IOExpoWordPressPostgreSQLPrismaPostgresNestJSMongoDBDockerTanStackTanStack TableRTK QueryGitGitHub
  • Introduction
  • Installation & Setup
  • Core Concepts
  • Column Definitions
  • Row Models
  • Sorting
  • Filtering
  • Pagination
  • Row Selection
  • Column Management
  • Server-side Mode
  • Advanced Features
  1. Docs
  2. TanStack Table
  3. Pagination — পেজ ভাগ
TanStack TableIntermediate

Pagination — পেজ ভাগ

pagination state, getPaginationRowModel, pageIndex 0-based, rowCount, getCanNextPage, page size বদলানো, filter বদলালে page reset — বাংলায়।

38টি অংশবাংলা ভাষায়

পেজিশন খুব সহজ মনে হয়, কিন্তু এখানে যে দুটো ভুল সবচেয়ে বেশি হয় সেগুলো একবার শুরুতেই পরিষ্কার করে নেওয়া দরকার — কারণ পরে চেষ্টা করেঠিক করা যায় না।

state

type PaginationState = {
  pageIndex: number; // 0 থেকে শুরু
  pageSize: number;
};

// ৫ পেজ, ৩ নম্বর পেজে আছি (মানে তৃতীয় পেজ)
{ pageIndex: 2, pageSize: 20 }

শূন্য থেকে শুরু — এটাই TanStack-এর নিয়ম, এবং এখানেই বেশিরভাগ বাগ। মানুষের স্বাভাবিক গণনা ১ থেকে শুরু, কিন্তু pageIndex: 0 মানে প্রথম পেজ।

সেটআপ

"use client";

import { useState } from "react";
import {
  getCoreRowModel,
  getPaginationRowModel,
  useReactTable,
  type PaginationState,
} from "@tanstack/react-table";

export function DataTable({ data, columns }: DataTableProps) {
  const [pagination, setPagination] = useState<PaginationState>({
    pageIndex: 0,
    pageSize: 10,
  });

  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    state: { pagination },
    onPaginationChange: setPagination,
  });
}
উপায়কখন
state + onPaginationChange (controlled)server-side, URL-এ রাখতে চাইলে
initialState (uncontrolled)শুধু client-side, state বাইরে দরকার নেই

পেজ কন্ট্রোল UI

pagination.tsxtsx
tsx
export function TablePagination({
  table,
}: {
  table: Table<Product>;
}) {
  const { pageIndex, pageSize } = table.getState().pagination;
  const pageCount = table.getPageCount();

  const visible = table.getRowModel().rows.length;

  const from = visible === 0 ? 0 : pageIndex * pageSize + 1;
  const to = pageIndex * pageSize + visible;
  const total = table.getFilteredRowModel().rows.length;

  return (
    <div className="flex items-center justify-between border-t p-3">
      <p className="text-sm text-muted-foreground">
        {from}–{to} / {total}টি
      </p>

      <div className="flex items-center gap-3">
        <div className="flex items-center gap-1">
          <Button
            variant="outline"
            size="icon-sm"
            onClick={() => table.setPageIndex(0)}
            disabled={!table.getCanPreviousPage()}
            aria-label="প্রথম পেজ"
          >
            <ChevronsLeft className="size-4" />
          </Button>
          <Button
            variant="outline"
            size="icon-sm"
            onClick={() => table.previousPage()}
            disabled={!table.getCanPreviousPage()}
            aria-label="আগের পেজ"
          >
            <ChevronLeft className="size-4" />
          </Button>

          <span className="px-2 text-sm">
            পেজ {pageIndex + 1} / {Math.max(pageCount, 1)}
          </span>

          <Button
            variant="outline"
            size="icon-sm"
            onClick={() => table.nextPage()}
            disabled={!table.getCanNextPage()}
            aria-label="পরের পেজ"
          >
            <ChevronRight className="size-4" />
          </Button>
          <Button
            variant="outline"
            size="icon-sm"
            onClick={() => table.setPageIndex(pageCount - 1)}
            disabled={!table.getCanNextPage()}
            aria-label="শেষ পেজ"
          >
            <ChevronsRight className="size-4" />
          </Button>
        </div>

        <Select
          value={String(pageSize)}
          onValueChange={(v) => table.setPageSize(Number(v))}
        >
          <SelectTrigger className="w-28">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            {[10, 20, 30, 50].map((size) => (
              <SelectItem key={size} value={String(size)}>
                {size}টি
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </div>
    </div>
  );
}

getCanNextPage — button কেন disabled

getPaginationRowModel() page count হিসাব করে current page-এর দিকে তাকিয়ে। তাই getCanNextPage()-এর উপর নির্ভর করে button disable করলে মিথ্যা চাপলেও কিছু হবে না।

table.getCanPreviousPage()   // pageIndex > 0
table.getCanNextPage()       // pageIndex < pageCount - 1
table.getPageCount()         // মোট কয়টি পেজ
table.getState().pagination  // { pageIndex, pageSize }

// সরাসরি নেভিগেশন
table.setPageIndex(3);
table.nextPage();
table.previousPage();
table.firstPage();
table.lastPage();

// page size
table.setPageSize(50);
table.resetPageIndex();   // মোট অবস্থা রিসেট
শেষ পেজ ছাড়া যাওয়া

মাঝে মাঝে কোনো ফিল্টার/সার্চে পেজ ৫-এ থেকে lastPage-এ যেতে চাইলে table.setPageIndex(Number.MAX_SAFE_INTEGER) দিলে table নিজে শেষ পেজে নিয়ে যাবে। শর্ত: manual mode নয় এবং rowCount ঠিক থাকতে হবে।

off-by-one — সবচেয়ে বড় ভুল

// UI তে দেখাতে হলে
const label = pageIndex + 1;   // 0 → "1"

// API-তে পাঠাতে হলে
const apiPage = pageIndex + 1; // 0 → 1  (১-ভিত্তিক API)

// ভুল
const apiPage = pageIndex;      // 0 → 0  ← প্রথম request-এ 0 পাঠাবে!
জায়গামান রাখার নিয়মউদাহরণ
state.pagination.pageIndex0-ভিত্তিকপ্রথম পেজ = 0
UI লেখা+ 1"পেজ 1"
?page= query param+ 1?page=1
row গণনা শুরুpageIndex * pageSize0, 20, 40…
API response-এসাধারণত 1-ভিত্তিক{ page: 1, total: 1284 }
// ৩য় পেজে কোন row দেখাবে
const pageIndex = 2;
const pageSize = 20;

const firstRowOnPage = pageIndex * pageSize;        // 40  (0-ভিত্তিক index)
const displayNumber  = firstRowOnPage + 1;         // 41  (মানুষ যা দেখে)
const lastRowOnPage  = (pageIndex + 1) * pageSize; // 60

Page size বদলালে pageIndex কী হয়

// pageSize 10 → 50 করলে
// pageIndex: 4 (5ম পেজ)
// → নতুন 50টির পেজে 4 আর নেই
// TanStack অটো-শুধু করে না যদি manual না হয়, কিন্তু যাচাই করা ভালো

const handlePageSizeChange = (size: number) => {
  setPagination((old) => {
    const maxIndex = Math.max(0, Math.ceil(total / size) - 1);
    return {
      pageSize: size,
      pageIndex: Math.min(old.pageIndex, maxIndex),
    };
  });
}

Filter বদলালে প্রথম পেজে ফেরা

Client mode-এ TanStack নিজেই autoResetPageIndex চালু রাখে — filter/sort বদলালে pageIndex 0 হয়ে যায়। কিন্তু manualPagination: true দিলে এটা নিজে থেকেই বন্ধ হয়ে যায়, কারণ server-এর পেজ গণনা table-এর জানা নেই। তখন এটা আপনার দায়িত্ব।

useEffect(() => {
  setPagination((old) =>
    old.pageIndex === 0 ? old : { ...old, pageIndex: 0 },
  );
}, [search, category, sortKey, sortOrder]);

old ফেরত দিলে state বদলায় না, তাই অপ্রয়োজনীয় re-render ও হয় না। এটা একটা খুব গুরুত্বপূর্ণ কৌশল — শর্তে old ফেরত দিলে React state unchanged ধরে নেয়।

rowCount — server mode-এর জন্য

useReactTable({
  data: data?.items ?? [],
  columns,
  getCoreRowModel: getCoreRowModel(),

  manualPagination: true,
  rowCount: data?.total ?? -1,   // ← মোট কত row আছে
});

rowCount ছাড়া table ধরে নেয় মাত্র data-র দৈর্ঘ্য — অর্থাৎ বর্তমান পেজের ২০টি। ফলে getPageCount() = ১, আর getCanNextPage() সবসময় false।

rowCountমানে
1234মোট ১২৩৪টি row জানা আছে
-1 (default)হিসাব করা হবে না, exact মান নেই
undefineddata.length ধরে নেওয়া হবে

যেসব API পরের পেজ আছে কিনা বলতে পারে (hasMore) সেখানে rowCount: -1 সঠিক — তখন শেষ পেজে getCanNextPage() দিয়ে নিয়ন্ত্রণ করুন।

API-তে পাঠানো

export function ProductsTable() {
  const [pagination, setPagination] = useState<PaginationState>({
    pageIndex: 0,
    pageSize: 10,
  });
  const [sorting, setSorting] = useState<SortingState>([]);

  const params = {
    page: pagination.pageIndex + 1,     // ← 0 → 1
    pageSize: pagination.pageSize,
    sortBy: sorting[0]?.id ?? "createdAt",
    sortOrder: sorting[0]?.desc ? "desc" : "asc",
  };

  const { data, isPending, isFetching } = useProducts(params);

  const table = useReactTable({
    data: data?.items ?? [],
    columns,
    getCoreRowModel: getCoreRowModel(),
    state: { pagination, sorting },
    onPaginationChange: setPagination,
    onSortingChange: setSorting,
    manualSorting: true,
    manualPagination: true,
    rowCount: data?.total ?? -1,
  });
}

এই সেতু — table-এর state থেকে query param, আর server-এর response থেকে row — এটাই server-side table-এর মূল। বিস্তারিত অধ্যায় ১১-এ।

সাধারণ বাগ

ঘটনাকারণসমাধান
০ পেজ দেখায়pageIndex সরাসরি API-তে+ 1 করুন
১ পেজের ডেটা দুবারUI-তে + 1 ভুলে দেনUI-তে pageIndex + 1
শেষ পেজে "Next" সক্রিয়rowCount দেওয়া হয়নিAPI-র total পাঠান
filter করলে খালি পেজmanual mode-এ auto reset বন্ধuseEffect-এ reset
pageSize বদলালে last pageঅপ্রয়োজনীয় indexMath.min দিয়ে clamp
getPaginationRowModel দেওয়া হয়নিসব row একসাথেযোগ করুন
পুরো row দেখাচ্ছেmanual mode-এ model দেওয়া আছেmodel বাদ দিন
manual mode-এ getPaginationRowModel দেবেন না

manualPagination: true দেওয়া আছে আর getPaginationRowModel()-ও আছে — এই দুটো একসাথে বিপরীত কাজ করে। getRowModel() ফিরিয়ে দেয় data-কেই (slice করা), আর button-গুলো data.length ধরে হিসাব করে, ফলে UI অসামঞ্জস্য হয়। manual মোডে শুধু getCoreRowModel() রাখুন।

সারসংক্ষেপ

  • pageIndex ০-ভিত্তিক, pageSize সাধারণ ১০/২০/৫০
  • UI ও API-তে সবসময় + 1
  • rowCount ছাড়া page count হিসাব হয় না
  • manual mode-এ page reset নিজে থেকে হয় না — করে দিতে হয়
  • page size বদলালে pageIndex clamp করুন

পরবর্তী

এখন user একাধিক row বেছে নিতে পারবে। পরের অধ্যায়ে row selection — indeterminate checkbox, bulk action নির্বাচন, আর select-all-এর সূক্ষ্ম বিষয়।

শেষ প্যারায় নিজে practice করুন — কোড কপি করে নিজের এডিটরে চালিয়ে দেখুন।
আগেরFiltering — খোঁজা ও ছাঁকাপরেরRow Selection — row বাছাই
এই পেজে
Tech Tutor

বাংলায় ওয়েব ডেভেলপমেন্ট ডকুমেন্টেশন শিখুন — ধাপে ধাপে।

Homeসব ডকুমেন্টেশনQuick Startআমার নোটস

© 2026 Tech Tutor. বাংলায় শিখুন, বাংলায় গড়ুন।

mdanamolhasan1998@gmail.com