Skip to Content
Module 8: UI Libraries8.2 Building Complex UI

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

A high-tech HUD interface displaying a complex "Data Table" with sortable columns and pagination controls. Holographic panels (Sheets/Dialogs) are sliding in from the sides. The design is sleek, using a monochromatic blue palette with neon highlights, emphasizing data organization and interactivity.

🛠️ 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 mechanism behind React’s most powerful table library (used under the hood by Shadcn Table).
  • Data Table: Build professional data tables with Sorting and Pagination features.
  • Overlays: Leverage Dialogs (Modals) and Sheets (Sidebars) to create a seamless user 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-table

Install Shadcn’s Table component:

npx shadcn-ui@latest add table

Exploded view of a Data Table. Layer 1 (Bottom): Raw JSON Data. Layer 2: TanStack Table Logic (Sorting, Filtering Engine). Layer 3: Shadcn UI Components (TableHeader, TableRow, TableCell). The layers stack up to form a finalized, polished UI component.

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 dialog box that appears in the center of the screen, requiring users to focus on and process it (e.g., Confirm Delete).
  • Sheet (Drawer): A panel that slides in from the edge of the screen, used for complex forms or filter menus.

Installation

npx shadcn-ui@latest add dialog sheet

Lab: 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

  1. TanStack Table + Shadcn: The perfect combination of powerful logic and a beautiful UI. We need to define columns and pass data ourselves.
  2. Pagination & Sorting: Come built-in with TanStack Table; we just need to connect functions like (getSortedRowModel, getPaginationRowModel) to the useReactTable hook.
  3. Overlays: Leverage Sheets for detailed editing forms to avoid interrupting the user’s context, and Dialogs for actions requiring immediate confirmation.

5. Resources

Last updated on