Managing data in React often involves presenting large datasets to users who need to find specific information quickly.
In my eight years of developing React applications, I’ve found that a well-designed table is only as good as its sorting capabilities.
One of the most efficient ways to handle this in the Material UI (MUI) ecosystem is by using the TableSortLabel component.
In this tutorial, I will show you exactly how to implement sorting in your MUI tables using real-world examples, like tracking US tech salaries or real estate data.
Use TableSortLabel in React MUI
The TableSortLabel is a specialized component that wraps table header text to provide a clickable area and a visual sort icon.
It handles the UI states for you, such as the “active” status and the “direction” (ascending or descending) of the sort arrow.
I prefer this over custom solutions because it aligns perfectly with Material Design principles and is highly accessible for screen readers.
Set Up a Basic Sortable Table
Let’s start with a practical example. Imagine we are building a dashboard to track job openings in various US tech hubs.
We want our users to be able to sort these jobs by the city name or the average annual salary.
First, you need to have @mui/material and @emotion/react installed in your project.
Here is the complete code to get a basic sortable table up and running:
import React, { useState } from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
TableSortLabel,
Box
} from '@mui/material';
// Sample data: US Tech Hubs and Salaries
const techJobs = [
{ id: 1, city: 'San Francisco', salary: 155000, state: 'California' },
{ id: 2, city: 'Austin', salary: 120000, state: 'Texas' },
{ id: 3, city: 'Seattle', salary: 145000, state: 'Washington' },
{ id: 4, city: 'New York City', salary: 150000, state: 'New York' },
{ id: 5, city: 'Denver', salary: 115000, state: 'Colorado' },
];
export default function SortableTechTable() {
const [order, setOrder] = useState('asc');
const [orderBy, setOrderBy] = useState('city');
const handleRequestSort = (property) => {
const isAsc = orderBy === property && order === 'asc';
setOrder(isAsc ? 'desc' : 'asc');
setOrderBy(property);
};
const sortedData = [...techJobs].sort((a, b) => {
if (order === 'asc') {
return a[orderBy] < b[orderBy] ? -1 : 1;
} else {
return a[orderBy] > b[orderBy] ? -1 : 1;
}
});
return (
<TableContainer component={Paper} sx={{ maxWidth: 800, margin: '20px auto' }}>
<Table>
<TableHead>
<TableRow>
<TableCell>
<TableSortLabel
active={orderBy === 'city'}
direction={orderBy === 'city' ? order : 'asc'}
onClick={() => handleRequestSort('city')}
>
City
</TableSortLabel>
</TableCell>
<TableCell align="right">
<TableSortLabel
active={orderBy === 'salary'}
direction={orderBy === 'salary' ? order : 'asc'}
onClick={() => handleRequestSort('salary')}
>
Avg Salary (USD)
</TableSortLabel>
</TableCell>
<TableCell>State</TableCell>
</TableRow>
</TableHead>
<TableBody>
{sortedData.map((row) => (
<TableRow key={row.id}>
<TableCell>{row.city}</TableCell>
<TableCell align="right">${row.salary.toLocaleString()}</TableCell>
<TableCell>{row.state}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}I executed the above example code and added the screenshot below.

In this code, I used the useState hook to keep track of which column is being sorted and the direction of the sort.
The TableSortLabel needs to know if it is active so it can highlight the text and show the arrow icon.
Advanced Method: Create a Reusable Sort Function
In professional projects, I rarely write the sort logic inside the component body like the example above.
It is much cleaner to create a helper function that can handle different data types, such as strings, numbers, or dates.
Below is a more robust method where we separate the sorting logic from the UI. This is particularly useful when dealing with US currency or complex zip codes.
import React, { useState } from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
TableSortLabel,
} from '@mui/material';
// Data: US Real Estate Listings
const propertyData = [
{ id: 1, address: '123 Maple St, Chicago', price: 450000, sqft: 2100 },
{ id: 2, address: '456 Oak Ave, Atlanta', price: 380000, sqft: 1800 },
{ id: 3, address: '789 Pine Rd, Miami', price: 620000, sqft: 2500 },
{ id: 4, address: '101 Cedar Ln, Phoenix', price: 410000, sqft: 1950 },
];
// Helper function for descending order
function descendingComparator(a, b, orderBy) {
if (b[orderBy] < a[orderBy]) return -1;
if (b[orderBy] > a[orderBy]) return 1;
return 0;
}
// Main sorting function
function getComparator(order, orderBy) {
return order === 'desc'
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy);
}
export default function RealEstateTable() {
const [order, setOrder] = useState('asc');
const [orderBy, setOrderBy] = useState('price');
const handleSort = (property) => {
const isAsc = orderBy === property && order === 'asc';
setOrder(isAsc ? 'desc' : 'asc');
setOrderBy(property);
};
return (
<TableContainer component={Paper} sx={{ mt: 4 }}>
<Table>
<TableHead sx={{ backgroundColor: '#f5f5f5' }}>
<TableRow>
<TableCell>
<TableSortLabel
active={orderBy === 'address'}
direction={orderBy === 'address' ? order : 'asc'}
onClick={() => handleSort('address')}
>
Property Address
</TableSortLabel>
</TableCell>
<TableCell align="right">
<TableSortLabel
active={orderBy === 'price'}
direction={orderBy === 'price' ? order : 'asc'}
onClick={() => handleSort('price')}
>
Listing Price
</TableSortLabel>
</TableCell>
<TableCell align="right">
<TableSortLabel
active={orderBy === 'sqft'}
direction={orderBy === 'sqft' ? order : 'asc'}
onClick={() => handleSort('sqft')}
>
Square Feet
</TableSortLabel>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{propertyData.sort(getComparator(order, orderBy)).map((row) => (
<TableRow key={row.id} hover>
<TableCell>{row.address}</TableCell>
<TableCell align="right">
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(row.price)}
</TableCell>
<TableCell align="right">{row.sqft.toLocaleString()} sq ft</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}I executed the above example code and added the screenshot below.

This approach is much more scalable. The descendingComparator is a standard pattern I use to ensure that the sort behaves predictably.
Notice how I used the Intl.NumberFormat for the price column; this ensures the table looks professional for a US audience.
Customize the Sort Icon
Sometimes, the default arrow icon doesn’t fit the application’s brand. MUI allows you to pass a custom icon to the TableSortLabel using the IconComponent prop.
In many corporate American apps, a double-arrow or a specific “up/down” caret is preferred.
import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
// Inside your TableCell
<TableSortLabel
active={orderBy === 'price'}
direction={orderBy === 'price' ? order : 'asc'}
onClick={() => handleSort('price')}
IconComponent={UnfoldMoreIcon}
>
Price
</TableSortLabel>Using a different icon can give your table a unique feel while still retaining all the built-in functionality of MUI.
Handle Sorting for Large Datasets (Server-side)
When you are dealing with thousands of rows, like a list of all retail stores in the USA, client-side sorting can make the browser lag.
In these cases, you should send the order and orderBy values to your backend API.
The TableSortLabel remains the same in the UI, but instead of sorting the array in React, you trigger a new fetch or axios call whenever the sort state changes.
I have found that combining TableSortLabel with a loading spinner provides the best user experience for heavy data tables.
Common Issues to Avoid
One mistake I see often is forgetting to set the active prop correctly. If active is not true, the sort arrow will be hidden by default until the user hovers over the header.
Always ensure your orderBy state matches the string you pass into the toggle function.
Another tip is to make sure your data types are consistent. If a column has both numbers and strings, the default sort might produce unexpected results.
Using TableSortLabel in React MUI is a simple way to make your data interactive and user-friendly.
Whether you are building a simple list or a complex financial dashboard, these patterns will help you create a polished product.
You may also like to read:
- React Class Component State
- How to Build Scheduling Calendar Component in React
- React Controlled vs Uncontrolled Components
- How to Pass Variable to React Component

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.