Back to cookbook

ChatGPT Prompt to Build a React Data Table With Sorting, Filtering, and Pagination

2 views Updated
Share

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) => (
<th
role="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>
<input
type="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.

Found this prompt useful? Share it.

Share

More in React Prompt Recipes

React Prompt Recipes

AI Prompt to Turn a Design Screenshot into a React Component

This is a React prompt for converting a design screenshot or mockup into working React component code — built for frontend developers who ge…

ROLE: You are a frontend developer converting a design image into a React component.

CONTEXT: I'm attaching a design screenshot/mockup. Tech stack: [REACT VERSION, STYLING APPROACH e.g. Tailwind/CSS Modules/styled-components, COMPONENT LIBRARY IF ANY]

TASK:
1. First, describe what you see in the image: overall layout, spacing, typography (font sizes/weights), colors, and any visible interactive states
2. Flag anything you're uncertain about (illegible text, ambiguous spacing, colors that are hard to read precisely) instead of guessing silently
3. Then write the React component matching this description, split into: component structure (JSX), styling, and any props needed to make it reusable

CONSTRAINTS:
- Match the layout and spacing as closely as possible to what's visible in the image
- Use [STYLING APPROACH] only, no inline styles unless the project convention requires it
- Make the component accept props for any content that would realistically vary (text, images, links)
- [ANY ADDITIONAL CONSTRAINT, e.g. must be accessible, must support dark mode]

OUTPUT FORMAT:
1. A short description of the design (layout, spacing, typography, colors, flagged uncertainties)
2. The complete component code in a fenced code block
3. A one-line note on anything that will need visual verification once rendered
React Prompt Recipes

AI Prompt to Build an Accessible React Modal Component

This prompt generates an accessible React modal component , built for frontend developers who need a dialog that actually works with keyboar…

ROLE: You are a senior frontend engineer writing accessible React components.

TASK:
Build a reusable Modal component in React with [TYPESCRIPT / JAVASCRIPT].

FUNCTIONAL REQUIREMENTS:
- Opens and closes based on an `isOpen` prop and `onClose` callback
- Renders via [REACT PORTAL / INLINE] into [TARGET DOM NODE OR DESCRIPTION]
- Closes on Escape key press
- Closes on clicking the overlay background (but not when clicking inside the modal content)

ACCESSIBILITY REQUIREMENTS:
- Traps keyboard focus inside the modal while it is open
- Returns focus to the element that triggered the modal when it closes
- Uses `role="dialog"`, `aria-modal="true"`, and `aria-labelledby` pointing to the modal title
- Prevents background content from being read by screen readers while the modal is open

STYLING:
- [UNSTYLED WITH CLASS NAMES / USE TAILWIND / MATCH THIS DESIGN SYSTEM: DESCRIBE]

OUTPUT FORMAT:
Provide the complete component code in a single file, followed by a short usage example showing how to open the modal from a parent component.
React Prompt Recipes

ChatGPT Prompt to Build a Reusable React Form Component with Validation

This ChatGPT prompt generates a reusable React form component with built in field validation, meant for frontend developers who want a worki…

Role: You are a frontend engineer who writes clean, reusable React components.

Context:
- Component purpose: [E.G., SIGNUP FORM, CONTACT FORM, SETTINGS FORM]
- Fields and validation rules: [FIELD NAME: TYPE AND RULE, repeat for each field, e.g., "email: required, must match email format"]
- State approach: [PLAIN useState, TYPESCRIPT, OR A LIBRARY LIKE REACT HOOK FORM]
- Styling approach: [CSS MODULES, TAILWIND, PLAIN CSS, OR INLINE STYLES]
- Submit behavior: [WHAT SHOULD HAPPEN ON VALID SUBMIT, E.G., "CALL AN onSubmit PROP WITH FORM VALUES"]

Constraints:
- Component must be self-contained and reusable, accepting props for initial values and onSubmit
- Each field must show its own inline error message that clears once the field becomes valid
- Submit button must be disabled while any required field is invalid

Task: Write the complete React component code implementing the form above.

Output format:
1. The full component code in a single fenced code block
2. A short explanation of the validation logic and how to use the component
3. Any assumptions made about props or styling