Module 8.2: Building Complex UI (Data Table, Sheet, Dialog)

🛠️ Image Assets Script
Run the following script in your terminal to create placeholder image files:
mkdir -p images/b08-2
touch images/b08-2/b08-2-banner.png
touch images/b08-2/b08-2-datatable-structure.png🎯 Learning Objectives
After this module, you’ll master:
- TanStack Table: Understand the
mechanismbehind React’s most powerful tablelibrary(usedunder the hoodby Shadcn Table). - Data Table: Build professional data tables with Sorting and Pagination features.
- Overlays: Leverage Dialogs (Modals) and Sheets (Sidebars) to create a
seamlessuser experience.
1. Data Table with TanStack Table
Shadcn/ui uses TanStack Table (formerly React Table) as its core. This is a “Headless” library – meaning it only handles the logic, without a UI. Shadcn provides the UI.
1.1. Installation
Install the core package:
npm install @tanstack/react-tableInstall Shadcn’s Table component:
npx shadcn-ui@latest add table
1.2. Defining Columns
Create src/payments/columns.tsx (e.g., for payment management).
import { ColumnDef } from '@tanstack/react-table';
import { ArrowUpDown } from 'lucide-react';
import { Button } from '@/components/ui/button';
// Define the data type for Payment
export type Payment = {
id: string,
amount: number,
status: 'pending' | 'processing' | 'success' | 'failed',
email: string,
};
export const columns: ColumnDef<Payment>[] = [
{
accessorKey: 'status',
header: 'Status',
},
{
accessorKey: 'email',
header: ({ column }) => {
return (
<Button variant="ghost" onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}>
Email
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
},
{
accessorKey: 'amount',
header: () => <div className="text-right">Amount</div>,
cell: ({ row }) => {
const amount = parseFloat(row.getValue('amount'));
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount);
return <div className="text-right font-medium">{formatted}</div>;
},
},
];2. Building the DataTable Component
Create src/components/DataTable.tsx. This component will be reused multiple times.
import * as React from 'react';
import {
ColumnDef,
flexRender,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
SortingState,
useReactTable,
} from '@tanstack/react-table';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Button } from '@/components/ui/button';
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
}
export function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = React.useState < SortingState > [];
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
state: {
sorting,
},
});
return (
<div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
{/* Pagination Controls */}
<div className="flex items-center justify-end space-x-2 py-4">
<Button variant="outline" size="sm" onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>
Previous
</Button>
<Button variant="outline" size="sm" onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}>
Next
</Button>
</div>
</div>
);
}3. Dialog & Sheet (Overlays)
- Dialog (Modal): A
dialogbox that appears in the center of the screen, requiring users to focus on and process it (e.g., Confirm Delete). - Sheet (Drawer): A
panelthat slides in from the edge of the screen, used for complex forms or filter menus.
Installation
npx shadcn-ui@latest add dialog sheetLab: Edit User with Sheet
In App.tsx, combine the DataTable and Sheet.
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
export default function App() {
return (
<div className="p-10">
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">Edit Profile (Open Sheet)</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Edit Profile</SheetTitle>
</SheetHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="name" className="text-right">
Name
</Label>
<Input id="name" value="Luan Nguyen" className="col-span-3" />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="username" className="text-right">
Username
</Label>
<Input id="username" value="@luanthnh" className="col-span-3" />
</div>
</div>
<Button type="submit">Save changes</Button>
</SheetContent>
</Sheet>
</div>
);
}4. Summary
- TanStack Table + Shadcn: The perfect combination of powerful logic and a beautiful
UI. We need to definecolumnsand passdataourselves. - Pagination & Sorting: Come built-in with TanStack Table; we just need to connect
functionslike (getSortedRowModel,getPaginationRowModel) to theuseReactTablehook. - Overlays: Leverage
Sheetsfor detailed editing forms to avoid interrupting the user’s context, andDialogsfor actions requiring immediate confirmation.
5. Resources
Last updated on