Claude Code से React टेबल कंपोनेंट बनाएं: sort, filter और pagination
Claude Code से React table बनाएं: sorting, filtering, pagination, mobile, TanStack Table और Playwright checks.
पहले table contract तय करें
Admin dashboard, customer list, billing history, product catalog और content analytics में table बार-बार आता है। शुरू में काम आसान लगता है: rows दिखा दो। लेकिन असली जरूरत जल्दी बढ़ती है: revenue के आधार पर sort करना, status से filter करना, लंबी list को pages में बांटना, mobile पर पढ़ने लायक बनाना, keyboard से चलाना और बदलाव के बाद Playwright से जांचना।
Claude Code इस काम को तेज कर सकता है, लेकिन prompt साफ होना चाहिए। अगर आप सिर्फ “एक अच्छी table बना दो” लिखेंगे, तो हो सकता है output में div grid हो, caption न हो, sort arrow दिखे लेकिन data sort न हो, या mobile view में column label गायब हो जाए। Table data relationship दिखाने वाला UI है, इसलिए HTML semantics, state, responsive behavior, accessibility और tests साथ में मांगना बेहतर है।
इस लेख में हम React/TypeScript का copy-pasteable table component बनाएंगे। इसमें semantic table, sortable columns, global filter, pagination, mobile CSS, accessibility review, TanStack Table कब चुनें, और Playwright checks शामिल हैं। React workflow के लिए Claude Code React development और accessibility के लिए Claude Code accessibility guide भी देखें।
Official references जरूर देखें: MDN <table>, MDN aria-sort, TanStack Table docs, Playwright Writing tests, और Claude Code overview।
Semantic table basics
जब row और column मिलकर अर्थ बनाते हैं, तब table सही choice है। Customer name, plan, MRR, status और signup date tabular data हैं, क्योंकि हर value अपने column header से समझ में आती है। अगर items अलग-अलग cards हैं, तो list या card grid बेहतर हो सकती है।
Basic structure में caption, thead, tbody और th scope="col" रखें। अगर पहली cell row को identify करती है, तो th scope="row" इस्तेमाल करें।
<table>
<caption>Customer के हिसाब से monthly recurring revenue</caption>
<thead>
<tr>
<th scope="col">Customer</th>
<th scope="col">Plan</th>
<th scope="col">MRR</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Northwind</th>
<td>Pro</td>
<td>$1,200</td>
</tr>
</tbody>
</table>
Claude Code को prompt देते समय लिखें कि native table structure, caption और scope attributes बने रहने चाहिए। इससे visual grid तो मिलता ही है, साथ में browser और assistive technology भी data relation समझते हैं।
flowchart TD
A["Requirements"] --> B["Semantic table"]
B --> C["Sort, filter, pagination"]
C --> D["Mobile layout"]
D --> E["Accessibility review"]
E --> F["Playwright checks"]
Claude Code prompt
Table component data, CSS, state और tests को जोड़ता है। इसलिए prompt में scope और ban list दोनों रखें।
React + TypeScript में customer table component बनाएं।
Requirements:
- केवल src/components/DataTable.tsx और src/components/data-table.css बदलें
- table, caption, thead, tbody और th scope इस्तेमाल करें
- Fields: id, name, plan, mrr, status, signedUpAt
- Global filter, column sorting और 5-row pagination जोड़ें
- aria-sort केवल currently sorted column पर लगाएं
- Column header के अंदर button से sorting करें
- Mobile पर data-label से cell labels दिखाएं
- Playwright test में filter, sort, pagination और mobile labels check करें
Do not:
- नई UI library add न करें
- role="grid" तब तक न लगाएं जब तक grid keyboard model implement न हो
- pseudocode न दें
इससे Claude Code को पता चलता है कि output सिर्फ सुंदर markup नहीं, बल्कि review-ready implementation होना चाहिए।
Copy-paste React/TypeScript implementation
यह implementation छोटी और मध्यम lists के लिए पर्याप्त है। इसमें filter बदलते समय page 1 पर वापस जाने का guard भी है।
// src/components/DataTable.tsx
"use client";
import { useMemo, useState, type ReactNode } from "react";
import "./data-table.css";
type SortDirection = "asc" | "desc";
type SortState<T> = { key: keyof T; direction: SortDirection } | null;
type Customer = {
id: string;
name: string;
plan: "Free" | "Pro" | "Enterprise";
mrr: number;
status: "active" | "trial" | "paused";
signedUpAt: string;
};
type Column<T> = {
key: keyof T;
label: string;
numeric?: boolean;
render?: (value: T[keyof T], row: T) => ReactNode;
};
const pageSize = 5;
const rows: Customer[] = [
{ id: "cus_001", name: "Northwind", plan: "Pro", mrr: 1200, status: "active", signedUpAt: "2026-01-15" },
{ id: "cus_002", name: "Blue Bottle", plan: "Free", mrr: 0, status: "trial", signedUpAt: "2026-02-02" },
{ id: "cus_003", name: "Kobayashi Studio", plan: "Enterprise", mrr: 8400, status: "active", signedUpAt: "2025-11-20" },
{ id: "cus_004", name: "Atlas Foods", plan: "Pro", mrr: 980, status: "paused", signedUpAt: "2025-12-09" },
{ id: "cus_005", name: "Green Lab", plan: "Pro", mrr: 1600, status: "active", signedUpAt: "2026-03-01" },
{ id: "cus_006", name: "Sakura Dental", plan: "Free", mrr: 0, status: "trial", signedUpAt: "2026-03-18" },
];
const money = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0,
});
const columns: Column<Customer>[] = [
{ key: "name", label: "Customer" },
{ key: "plan", label: "Plan" },
{ key: "mrr", label: "MRR", numeric: true, render: (_, row) => money.format(row.mrr) },
{ key: "status", label: "Status" },
{ key: "signedUpAt", label: "Signed up", render: (_, row) => new Date(row.signedUpAt).toLocaleDateString("en-US") },
];
function compare<T>(a: T, b: T, key: keyof T) {
const left = a[key];
const right = b[key];
if (typeof left === "number" && typeof right === "number") return left - right;
return String(left).localeCompare(String(right), undefined, { numeric: true, sensitivity: "base" });
}
export function DataTable() {
const [query, setQuery] = useState("");
const [page, setPage] = useState(1);
const [sort, setSort] = useState<SortState<Customer>>({ key: "name", direction: "asc" });
const filtered = useMemo(() => {
const keyword = query.trim().toLowerCase();
if (!keyword) return rows;
return rows.filter((row) =>
columns.some((column) => String(row[column.key]).toLowerCase().includes(keyword)),
);
}, [query]);
const sorted = useMemo(() => {
if (!sort) return filtered;
return [...filtered].sort((a, b) => {
const result = compare(a, b, sort.key);
return sort.direction === "asc" ? result : -result;
});
}, [filtered, sort]);
const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));
const currentPage = Math.min(page, totalPages);
const pageRows = sorted.slice((currentPage - 1) * pageSize, currentPage * pageSize);
function updateQuery(value: string) {
setQuery(value);
setPage(1);
}
function toggleSort(key: keyof Customer) {
setSort((current) => {
if (!current || current.key !== key) return { key, direction: "asc" };
return { key, direction: current.direction === "asc" ? "desc" : "asc" };
});
}
return (
<section className="table-shell" aria-labelledby="customers-title">
<label>
<span>Filter customers</span>
<input value={query} onChange={(event) => updateQuery(event.target.value)} type="search" />
</label>
<div className="table-scroll" tabIndex={0}>
<table className="data-table">
<caption id="customers-title">Monthly recurring revenue by customer</caption>
<thead>
<tr>
{columns.map((column) => {
const isSorted = sort?.key === column.key;
const ariaSort = isSorted ? (sort.direction === "asc" ? "ascending" : "descending") : undefined;
return (
<th key={String(column.key)} scope="col" aria-sort={ariaSort} className={column.numeric ? "numeric" : undefined}>
<button type="button" onClick={() => toggleSort(column.key)}>{column.label}</button>
</th>
);
})}
</tr>
</thead>
<tbody>
{pageRows.map((row) => (
<tr key={row.id}>
{columns.map((column, index) => {
const content = column.render ? column.render(row[column.key], row) : String(row[column.key]);
return index === 0 ? (
<th key={String(column.key)} scope="row" data-label={column.label}>{content}</th>
) : (
<td key={String(column.key)} data-label={column.label} className={column.numeric ? "numeric" : undefined}>{content}</td>
);
})}
</tr>
))}
</tbody>
</table>
</div>
<nav className="pagination" aria-label="Table pagination">
<button type="button" disabled={currentPage === 1} onClick={() => setPage((value) => value - 1)}>Previous</button>
<span aria-live="polite">Page {currentPage} of {totalPages}</span>
<button type="button" disabled={currentPage === totalPages} onClick={() => setPage((value) => value + 1)}>Next</button>
</nav>
</section>
);
}
Mobile CSS और accessibility
Narrow screen पर table को card-like layout में दिखा सकते हैं, लेकिन DOM में table structure बना रहना चाहिए।
.table-scroll {
overflow-x: auto;
}
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th,
.data-table td {
border-top: 1px solid #e5e7eb;
padding: 0.75rem;
text-align: left;
}
.pagination {
display: flex;
gap: 0.75rem;
justify-content: flex-end;
}
@media (max-width: 640px) {
.data-table thead {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
}
.data-table,
.data-table tbody,
.data-table tr,
.data-table th,
.data-table td {
display: block;
width: 100%;
}
.data-table th,
.data-table td {
display: grid;
grid-template-columns: 8rem 1fr;
gap: 0.75rem;
}
.data-table th::before,
.data-table td::before {
content: attr(data-label);
font-weight: 700;
}
}
Accessibility checklist में caption, scope, sorting button, aria-sort, filter label और pagination के aria-live को देखें। role="grid" तभी इस्तेमाल करें जब spreadsheet जैसा keyboard behavior भी implement कर रहे हों।
TanStack Table कब चुनें
Simple list के लिए custom component ठीक है। लेकिन column visibility, per-column filters, row selection, server-side pagination, pinned columns या virtualization चाहिए, तो TanStack Table बेहतर है। यह headless library है: logic देती है, UI आप अपने design system से बनाते हैं।
| Use case | कौन सा तरीका | ध्यान देने वाली बात |
|---|---|---|
| छोटी admin list | Custom component | कम dependency, पर feature खुद maintain करना होगा |
| Complex dashboard | TanStack Table | API सीखनी होगी |
| Spreadsheet जैसा editor | Enterprise grid | bundle size और license देखें |
Claude Code से कहें कि dependency जोड़ने से पहले कारण बताए। हर table को heavy grid की जरूरत नहीं होती।
Playwright checks
Table test में सिर्फ page load नहीं, workflow check करें।
// tests/customer-table.spec.ts
import { expect, test } from "@playwright/test";
test("customer table works", async ({ page }) => {
await page.goto("/customers");
await expect(page.getByRole("table", { name: /monthly recurring revenue/i })).toBeVisible();
await page.getByRole("button", { name: /MRR/ }).click();
await expect(page.getByRole("columnheader", { name: /MRR/ })).toHaveAttribute("aria-sort", "ascending");
await page.getByLabel("Filter customers").fill("north");
await expect(page.getByRole("row", { name: /Northwind/ })).toBeVisible();
await page.getByLabel("Filter customers").fill("");
await page.getByRole("button", { name: "Next" }).click();
await expect(page.getByText("Page 2 of 2")).toBeVisible();
await page.setViewportSize({ width: 390, height: 844 });
await expect(page.locator("td[data-label='Plan']").first()).toBeVisible();
});
Claude Code को bug fix देते समय यह test भी दें और लिखें कि पहले failing case reproduce करे।
Use cases, pitfalls और monetization CTA
| Use case | Table features | Value |
|---|---|---|
| SaaS customer list | Plan, MRR, status, renewal date | churn risk और upsell candidates दिखते हैं |
| Ecommerce catalog | Stock, price, category, publish state | stock और price mistakes जल्दी मिलती हैं |
| Content dashboard | PV, read rate, CTA clicks, update date | rewrite priority और ad revenue decisions सुधरते हैं |
| Billing history | Payment status, amount, due date | support समय घटता है |
Common pitfalls हैं: div से fake table बनाना, sort arrow दिखाना पर aria-sort भूलना, filter पर page 1 न करना, mobile को अंत में जोड़ना, और Claude Code से unnecessary UI library install करा लेना।
Table monetization में मदद करती है क्योंकि next action साफ दिखता है। Content site में traffic, CTA clicks और article revenue साथ रखें। SaaS में MRR, usage drop और renewal date साथ रखें। Team workflow के लिए Claude Code training and consulting देखें। अकेले सीखने के लिए products and templates से शुरुआत करें।
Tested result
Masa ने यह pattern एक छोटी customer list में आजमाया। सबसे उपयोगी सुधार था filter बदलते ही page 1 पर लौटना; पहले page 2 पर search करने से result होने के बाद भी empty screen दिखती थी। दूसरा फायदा था शुरू से data-label जोड़ना, जिससे mobile CSS बाद में patch नहीं करना पड़ा। Claude Code को semantics, state, mobile, accessibility और Playwright एक साथ बताने पर review आसान हुआ।
मुफ़्त PDF: Claude Code cheatsheet
Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.
हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.
लेखक के बारे में
Masa
Claude Code workflow और team adoption पर काम करने वाला engineer.
संबंधित लेख
Claude Code permission safety ladder: access धीरे-धीरे बढ़ाएं
read-only से limited edits, proof commands और deploy checks तक permission बढ़ाने की सुरक्षित ladder.
Claude Code Small PR Proof Pack: छोटे PR को review-ready बनाना
Claude Code PR के लिए diff, checks, public URL, CTA path और rollback वाला practical proof pack.
Claude Code Review Gate Before Commit: diff, test, public URL और CTA जांच
Claude Code से commit से पहले review gate बनाएं: diff, build, public URL, Gumroad, consultation, tests और unrelated files।