The React TanStack table is a great tool for displaying an elegant view of data table. It provides enriched features to build list with impressive user experience.
This tutorial uses Server-side data fetching and rendering, searching and sorting features.
A shopping cart product table is created in the backend. It has basic details to be listed in a tabular form. The React app connects to the PHP API to connect this database to fetch the rows.
The React TanStack data table shows these data rows in the front end as shown below.

This command is used to install the React TanStack data table library.
npm install @tanstack/react-table
This will add the following library reference to the application’s package config.
npm install @tanstack/react-table "dependencies": {
"@tanstack/react-table": "^8.21.3",
...
...
},
If you are going to run this example in your environment, follow the below steps to set up. It has all the build required for the React TanStack data table rendering.
cd react-data-table-server-side-pagination-sorting-searchnpm install to create node_modules./api/react-product-table-api/ to your PHP root and configure in React JSX.db_shopping_cart and import sql/database.sqlThen, start the dev server by using npm run dev.
This is to initiate React TabStack library using useReactTable. It adds the raw data reference to the TanStack library row column object to render data.
Part of TanStack.jsx
import { useEffect, useState, useCallback } from "react";
import axios from "axios";
import {
useReactTable,
getCoreRowModel,
getSortedRowModel,
flexRender,
} from "@tanstack/react-table";
import "./TanStack.css";
const TanStack = () => {
const [data, setData] = useState([]);
const [search, setSearch] = useState("");
const [page, setPage] = useState(1);
const [limit, setLimit] = useState(5);
const [total, setTotal] = useState(0);
const [sortBy, setSortBy] = useState("id");
const [order, setOrder] = useState("asc");
...
//Fetch Data
...
useEffect(() => {
fetchData();
}, [fetchData]);
const columns = [
{
header: "S.No",
accessorKey: "sno",
cell: (info) => (page - 1) * limit + info.row.index + 1,
},
{
header: "Title",
accessorKey: "title",
cell: (info) => info.getValue(),
},
{
header: "Price",
accessorKey: "price",
cell: (info) => `$${info.getValue()}`,
},
{
header: "Category",
accessorKey: "category",
cell: (info) => info.getValue(),
},
];
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
});
...
...
}
There are three major UI parts in this React data table example.

Table header - Part of TanStack.jsx
<th key={header.id}
className={alignClass}
onClick={() =>
key !== "sno" && handleSort(key)
}
style={{
cursor: key === "sno" ? "default" : "pointer",
}}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{key !== "sno" &&
sortBy === key &&
(order === "asc" ? (
<img
src="/icons/up.svg"
alt="asc"
style={{
width: "14px",
height: "14px",
marginLeft: "5px",
verticalAlign: "middle",
}}
/>
) : (
<img
src="/icons/down.svg"
alt="desc"
style={{
width: "14px",
height: "14px",
marginLeft: "5px",
verticalAlign: "middle",
}}
/>
))}
</th>
This section has the TanStack script for iterating the table row array using table.gerRowModel() reference.
The flexRender function loads the table cell data in a row.
Table body rows in TanStack.jsx
<tbody>
{table.getRowModel().rows.length > 0 ? (
table.getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => {
const key = cell.column.columnDef.accessorKey;
const alignClass =
key === "price" ? "price" :
key === "sno" ? "center" : "";
return (
<td key={cell.id} className={alignClass}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
);
})}
</tr>
))
) : (
<tr>
<td colSpan={columns.length}>No data found</td>
</tr>
)}
</tbody>
It is a usual HTML input that sets the search keyword on-change. When the search is on, it resets the current-page value back to 1.
Search input JSX in TanStack.jsx
<div className="search-container">
<img src="/icons/search.svg" alt="search" className="search-icon" />
<input
type="text"
placeholder="Search by title..."
value={search}
onChange={(e) => {
setSearch(e.target.value);
setPage(1);
}}
className="search-input"
/>
</div>
This component returns JSX with numbered buttons with a previous-next pagination link to move the row pointer back and forth.
Pagination JSX
<div className="pagination-container">
<button
className="pagination-btn"
onClick={() => setPage((prev) => Math.max(prev - 1, 1))}
disabled={page === 1}
>
<img
src="/icons/back.svg"
alt="Previous"
className="pagination-icon"
/>
Prev
</button>
<span className="pagination-info">
Page {page} of {totalPages || 1}
</span>
<button
className="pagination-btn"
onClick={() => setPage((prev) => (prev < totalPages ? prev + 1 : prev))}
disabled={page >= totalPages}
>
Next
<img
src="/icons/forward.svg"
alt="Next"
className="pagination-icon"
/>
</button>
<select
value={limit}
onChange={(e) => {
setLimit(Number(e.target.value));
setPage(1);
}}
className="pagination-select"
>
{[5, 10, 20, 50].map((num) => (
<option key={num} value={num}>
{num} / page
</option>
))}
</select>
</div>

This is the place to configure the server-side API URL to connect to the database for displaying dynamic grid. It sets the data array for the TanStack initiation.
This example has a single endpoint that receives all the search, sort and pagination inputs. The following React useCallback() passes these parameters to the API.
The React callback hook sets the raw data for the TanStack’s getCoreRowModel block to build a table row instance.
Part of TanStack.jsx
const fetchData = useCallback(async () => {
try {
const res = await axios.get("http://localhost/react-product-table-api/products.php", {
params: { search, page, limit, sortBy, order },
});
const fetchedData = res.data.data || res.data.products || [];
setData(fetchedData);
setTotal(res.data.total || 0);
} catch (err) {
console.error("Error fetching data:", err);
}
}, [search, page, limit, sortBy, order]);
Below PHP script shows how to process a request from the allowed React origin. This PHP service prepares the SELECT query based on the search keyword, current page and sorting column & order passed from the React frontend.
react-product-table-api/products.php
<?php
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json");
require_once "db.php";
$page = $_GET['page'] ?? 1;
$limit = $_GET['limit'] ?? 5;
$search = $_GET['search'] ?? '';
$sortBy = $_GET['sortBy'] ?? 'id';
$order = $_GET['order'] ?? 'asc';
$offset = ($page - 1) * $limit;
$allowedSort = ['id', 'title', 'price', 'category'];
$allowedOrder = ['asc', 'desc'];
if (!in_array($sortBy, $allowedSort)) $sortBy = 'id';
if (!in_array(strtolower($order), $allowedOrder)) $order = 'asc';
$sql = "SELECT * FROM products WHERE title LIKE ? ORDER BY $sortBy $order LIMIT ?, ?";
$stmt = $conn->prepare($sql);
$searchParam = "%$search%";
$stmt->bind_param("sii", $searchParam, $offset, $limit);
$stmt->execute();
$result = $stmt->get_result();
$data = [];
while ($row = $result->fetch_assoc()) {
$data[] = $row;
}
$countSql = "SELECT COUNT(*) AS total FROM products WHERE title LIKE ?";
$countStmt = $conn->prepare($countSql);
$countStmt->bind_param("s", $searchParam);
$countStmt->execute();
$countResult = $countStmt->get_result()->fetch_assoc();
echo json_encode([
"products" => $data,
"total" => $countResult["total"]
]);
?>
This example built a front end React data table with search, sort and pagination features. The TanStack table library provides more features apart these three features. For example, it has column ordering, row selection, resizing and more. But, we covered the prime features of this library that are need in majority of the frontend tables.
References