In this blog, you’ll learn how to set up a React app using Vite with Shadcn and TanStack Table for creating a reusable table component. Let’s get started!
1. Create the React App with Vite:
Replace the [project-name] with your actual project name.
npm create vite@latest [project-name] -- --template react-ts --swc
cd [project-name]
npm install2. Install Shadcn and @TanStack/react-table:
2.1. Shadcn:
2.1.1. Install Tailwind CSS:
npm install tailwindcss @tailwindcss/vite2.1.2. Configure the Vite plugin:
Install @types/node first:
npm install --save-dev @types/nodeGo to vite.config.ts file and set the configuration:
import path from 'path'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})2.1.3. Configure tsconfig.json file:
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}2.1.4. Configure tsconfig.app.json file:
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
/* Shadcn Config */
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"]
}2.1.5. Import Tailwind CSS:
@import 'tailwindcss';2.1.6. Run the Shadcn CLI:
npx shadcn@latest init2.1.7. Install Table, Pagination and Buttom component from Shadcn:
npx shadcn@latest add button table pagination2.2. @TanStack/react-table:
2.2.1 Install @TanStack/react-table:
npm install @tanstack/react-table3. Create the Table Component:
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 './ui/table'
import { Button } from './ui/button'
import { ArrowDown, ArrowUp, ArrowUpDown } from 'lucide-react'
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from './ui/pagination'
interface PaginationComponentProps {
currentPage: number
totalPages: number
onPageChange: (page: number) => void
}
const PaginationComponent = ({
currentPage,
totalPages,
onPageChange,
}: PaginationComponentProps) => {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
onClick={(e) => {
e.preventDefault()
if (currentPage > 1) onPageChange(currentPage - 1)
}}
/>
</PaginationItem>
{[...Array(totalPages)].map((_, index) => {
const page = index + 1
if (
page === 1 ||
page === totalPages ||
(page >= currentPage - 1 && page <= currentPage + 1)
) {
return (
<PaginationItem key={page}>
<PaginationLink
href="#"
isActive={page === currentPage}
onClick={(e) => {
e.preventDefault()
onPageChange(page)
}}
>
{page}
</PaginationLink>
</PaginationItem>
)
} else if (page === currentPage - 2 || page === currentPage + 2) {
return <PaginationEllipsis key={page} />
}
return null
})}
<PaginationItem>
<PaginationNext
href="#"
onClick={(e) => {
e.preventDefault()
if (currentPage < totalPages) onPageChange(currentPage + 1)
}}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
)
}
interface TableComponentProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
pagination?: {
page: number
perPage: number
totalPages: number
onPageChange: (page: number) => void
}
sorting?: SortingState
setSorting?: React.Dispatch<React.SetStateAction<SortingState>>
}
export const TableComponent = <TData, TValue>({
columns,
data,
pagination,
sorting,
setSorting,
}: TableComponentProps<TData, TValue>) => {
const table = useReactTable({
data: data,
columns: columns,
getCoreRowModel: getCoreRowModel(),
...(pagination
? {
getPaginationRowModel: getPaginationRowModel(),
state: {
pagination: {
pageIndex: pagination.page - 1,
pageSize: pagination.perPage,
},
},
manualPagination: true,
pageCount: pagination.totalPages,
}
: {}),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
...(sorting ? { state: { sorting: sorting } } : {}),
})
return (
<div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
const isSortable = header.column.getCanSort()
return (
<TableHead key={header.id}>
{isSortable ? (
<Button
variant="ghost"
onClick={() => header.column.toggleSorting()}
>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{header.column.getIsSorted() === 'asc' ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : header.column.getIsSorted() === 'desc' ? (
<ArrowDown className="ml-2 h-4 w-4" />
) : (
<ArrowUpDown className="ml-2 h-4 w-4" />
)}
</Button>
) : (
flexRender(
header.column.columnDef.header,
header.getContext(),
)
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id} className="text-left">
{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 && (
<div className="flex items-center justify-end space-x-2 py-4">
<PaginationComponent
currentPage={pagination.page}
totalPages={pagination.totalPages}
onPageChange={pagination.onPageChange}
/>
</div>
)}
</div>
)
}4. Example:
import { useState } from 'react'
import './App.css'
import { TableComponent } from './components/tablecomponent'
import { ColumnDef } from '@tanstack/react-table'
interface User {
id: number
name: string
email: string
role: string
}
const columns: ColumnDef<User>[] = [
{
accessorKey: 'name',
header: 'Name',
cell: (info) => {
const name = info.row.original.name
return <div>{name}</div>
},
enableSorting: false,
},
{
accessorKey: 'email',
header: 'Email',
cell: (info) => {
const email = info.row.original.email
return <div>{email}</div>
},
enableSorting: false,
},
{
accessorKey: 'role',
header: 'Role',
cell: (info) => {
const role = info.row.original.role
return <div>{role}</div>
},
enableSorting: false,
},
]
const generateUsers = (count: number): User[] => {
return Array.from({ length: count }, (_, i) => ({
id: i + 1,
name: `User ${i + 1}`,
email: `user${i + 1}@example.com`,
role: i % 3 === 0 ? 'Admin' : i % 3 === 1 ? 'Editor' : 'Viewer',
}))
}
function App() {
const [data] = useState<User[]>(() => generateUsers(50))
const [currentPage, setCurrentPage] = useState(1)
const [itemsPerPage] = useState(10)
const totalPages = Math.ceil(data.length / itemsPerPage)
const getCurrentPageData = () => {
const start = (currentPage - 1) * itemsPerPage
const end = start + itemsPerPage
return data.slice(start, end)
}
const handlePageChange = (page: number) => {
setCurrentPage(page)
}
return (
<TableComponent
columns={columns}
data={getCurrentPageData()}
pagination={{
page: currentPage,
perPage: itemsPerPage,
totalPages: totalPages,
onPageChange: handlePageChange,
}}
/>
)
}
export default App