ChatGPT Prompt to Build a React Data Table With Sorting, Filtering, and Pagination
This ChatGPT prompt is for frontend developers who need a working React data table component — the kind with sortable columns, a search/filter input, and pagination controls — without manually wiring up the state logic from scratch every time. It's built for anyone who has typed "build me a React table" into a chat window and gotten back a bare <table> with no interactivity, then had to ask three follow-up questions to get sorting and pagination added.
The prompt works by specifying the component's props, state shape, and interaction behavior up front: what columns are sortable, how filtering should match (exact, partial, case-insensitive), and how many rows per page. Giving the model this structure before it writes any code means you get a complete, working component on the first pass instead of a skeleton you have to extend across several more turns.
Because data tables tend to accumulate edge cases fast — empty datasets, a single row, filtered results that return nothing — it's worth running the generated component's prompt through Prompt Debugger before you rely on it, so those states are specified rather than left to the model's improvisation.
Prompt template
ROLE: You are a senior frontend engineer who writes clean, accessible React components.
CONTEXT: I need a React component called [COMPONENT_NAME] that displays a data table for the following data shape: [DESCRIBE_DATA_SHAPE_E.G._ARRAY_OF_OBJECTS_WITH_FIELDS_AND_TYPES]
REQUIREMENTS:
- Columns: [LIST_COLUMN_NAMES_AND_WHICH_ARE_SORTABLE]
- Sorting: clicking a sortable column header toggles ascending/descending order, with a visible indicator of current sort state
- Filtering: a single search input that filters rows by [SPECIFY_WHICH_FIELDS_TO_MATCH], case-insensitive
- Pagination: [NUMBER] rows per page, with previous/next controls and a current page indicator
- Empty states: show a clear message when the data array is empty, and a separate message when a filter returns no results
- Accessibility: sortable headers must be keyboard-operable and expose their sort state to screen readers
- Styling approach: [SPECIFY_E.G._TAILWIND_CLASSES_OR_PLAIN_CSS_MODULES]
OUTPUT FORMAT: Return the complete component code in a single code block, followed by a short list of any assumptions you made about data or behavior that I didn't specify.
Example input
ROLE: You are a senior frontend engineer who writes clean, accessible React components.
CONTEXT: I need a React component called CustomerTable that displays a data table for the following data shape: array of objects: { id: string, name: string, email: string, plan: 'free' | 'pro' | 'enterprise', signupDate: string (ISO date), mrr: number }
REQUIREMENTS:
- Columns: Name (sortable), Email, Plan, Signup Date (sortable), MRR (sortable)
- Sorting: clicking a sortable column header toggles ascending/descending order, with a visible indicator of current sort state
- Filtering: a single search input that filters rows by name and email, case-insensitive
- Pagination: 15 rows per page, with previous/next controls and a current page indicator
- Empty states: show a clear message when the data array is empty, and a separate message when a filter returns no results
- Accessibility: sortable headers must be keyboard-operable and expose their sort state to screen readers
- Styling approach: Tailwind CSS classes
OUTPUT FORMAT: Return the complete component code in a single code block, followed by a short list of any assumptions you made about data or behavior that I didn't specify.
Example output
import { useState, useMemo } from 'react';function CustomerTable({ customers }) {const [search, setSearch] = useState('');const [sortField, setSortField] = useState('name');const [sortDir, setSortDir] = useState('asc');const [page, setPage] = useState(1);const pageSize = 15;const filtered = useMemo(() => {const q = search.toLowerCase();return customers.filter(c =>c.name.toLowerCase().includes(q) || c.email.toLowerCase().includes(q));}, [customers, search]);const sorted = useMemo(() => {return [...filtered].sort((a, b) => {const dir = sortDir === 'asc' ? 1 : -1;return a[sortField] > b[sortField] ? dir : -dir;});}, [filtered, sortField, sortDir]);const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));const pageItems = sorted.slice((page - 1) * pageSize, page * pageSize);const toggleSort = (field) => {if (sortField === field) {setSortDir(d => (d === 'asc' ? 'desc' : 'asc'));} else {setSortField(field);setSortDir('asc');}};const sortableHeader = (label, field) => (<throle="columnheader"aria-sort={sortField === field ? (sortDir === 'asc' ? 'ascending' : 'descending') : 'none'}tabIndex={0}onClick={() => toggleSort(field)}onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && toggleSort(field)}className="cursor-pointer px-4 py-2 text-left select-none">{label} {sortField === field ? (sortDir === 'asc' ? '▲' : '▼') : ''}</th>);if (customers.length === 0) {return <p className="p-4 text-gray-500">No customers yet.</p>;}return (<div><inputtype="text"value={search}onChange={(e) => { setSearch(e.target.value); setPage(1); }}placeholder="Search by name or email"className="mb-3 border px-3 py-2 rounded w-full max-w-sm"/>{sorted.length === 0 ? (<p className="p-4 text-gray-500">No results match your search.</p>) : (<><table className="w-full border-collapse"><thead><tr className="border-b">{sortableHeader('Name', 'name')}<th className="px-4 py-2 text-left">Email</th><th className="px-4 py-2 text-left">Plan</th>{sortableHeader('Signup Date', 'signupDate')}{sortableHeader('MRR', 'mrr')}</tr></thead><tbody>{pageItems.map((c) => (<tr key={c.id} className="border-b"><td className="px-4 py-2">{c.name}</td><td className="px-4 py-2">{c.email}</td><td className="px-4 py-2">{c.plan}</td><td className="px-4 py-2">{c.signupDate}</td><td className="px-4 py-2">${c.mrr}</td></tr>))}</tbody></table><div className="flex items-center gap-2 mt-3"><button disabled={page === 1} onClick={() => setPage(p => p - 1)} className="px-3 py-1 border rounded disabled:opacity-50">Previous</button><span>Page {page} of {totalPages}</span><button disabled={page === totalPages} onClick={() => setPage(p => p + 1)} className="px-3 py-1 border rounded disabled:opacity-50">Next</button></div></>)}</div>);}export default CustomerTable;
Assumptions made:
- Email column is not sortable or filterable beyond the search box, per your spec
- MRR is displayed with a plain dollar prefix with no decimal formatting since currency formatting wasn't specified
- Page reset to 1 whenever the search term changes, to avoid landing on an empty page
When to use it
- Scaffolding an admin dashboard or internal tool that needs a sortable, filterable list view
- Replacing a static HTML table with an interactive one without hand-writing sort and pagination logic
- Prototyping a data-heavy UI quickly before deciding on a full table library
- Teaching or demonstrating React state management patterns using a realistic, non-trivial component
Best practices
- Specify the exact shape of the data array (field names and types) so the model doesn't invent mismatched prop names
- State which columns need custom rendering, such as a date formatted as relative time or a status shown as a colored badge
- Ask for the empty-state and no-results-after-filtering states explicitly, since models often skip them unless told
- Request TypeScript types for props and state up front if your codebase uses TypeScript, rather than adding them after the fact
Common mistakes
- Not specifying page size, which leaves the model guessing between 10, 20, or 25 rows per page
- Leaving sort direction unspecified, so ascending/descending toggling behavior is inconsistent across columns
- Forgetting to ask for keyboard and screen-reader accessibility on sortable column headers
- Accepting the first version without testing it against an empty array or a single-row dataset
FAQs
How do I get ChatGPT to generate a React table with working sort and pagination in one pass?
Specify the exact data shape, which columns are sortable, the page size, and how filtering should match text, all before the model writes any code. Vague requests like "add sorting" tend to produce inconsistent behavior across columns because the model has to guess your intent for each one.
Should I ask for TypeScript types in the same prompt as the component?
Yes, if your project uses TypeScript, ask for prop and state types in the same request rather than adding them afterward. Retrofitting types onto an already-generated component often requires more back-and-forth than specifying them up front.
Why does the generated table break when the data array is empty?
Many generated components assume there's always at least one row, so sorting or pagination logic can throw or render incorrectly on an empty array. Explicitly requesting an empty-state message in the prompt is the most reliable fix.
Can this same prompt structure work for a table with server-side pagination instead of client-side?
Yes, but you need to change the requirements section to describe the API contract (what parameters the server expects for page, sort field, and sort direction) instead of assuming all data is already loaded client-side. The sorting and filtering logic then moves from the component to the API call.
What tool pairs well with this prompt for catching missing edge cases before I use it?
Prompt Debugger — it flags vague constraints and unhandled edge cases, like unspecified empty states or ambiguous sort behavior, that would otherwise show up as bugs after the component is already in your codebase.