Reputation: 45
How to implement custom asc and desc sorting?
I want to sort the "status field" in such a way that only numeric values are sorted, while blank values remain unchanged and are always placed at the end.
Like this:
asc [2,'',4,'',3] => [2,3,4,'','']
desc [2,'',4,'',3] => [4,3,2,'','']
false [2,'',4,'',3] = [2,'',4,'',3]
I tried handling the data manually outside, meaning rearranging the array manually. However, this causes an issue as column.getIsSorted() cannot retrieve the sorted status.
I hope to achieve this functionality with as little modification as possible or minimal changes to the DataTable component.
I've looked at the documentation for the tanstack table, such as the usage of sortingFn, but I still can't figure out the correct approach to achieve this.
Here's my code:
'use client';
import {
Column,
ColumnDef,
SortingState,
flexRender,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { useEffect, useState } from 'react';
import { Button } from './components/ui/button';
import {
ArrowDownNarrowWide,
ArrowUpNarrowWide,
ArrowDownUp,
} from 'lucide-react';
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
}
type Order = {
id: string;
amount: number;
status: number | '';
email: string;
};
function getProcessedData(data: Order[], direction: 'asc' | 'desc' | '') {
if (direction === '') {
return data;
}
const StatusData = data
.filter((item) => item.status !== '')
.sort((a, b) => {
if (direction === 'asc') {
return a.status - b.status;
}
if (direction === 'desc') {
return b.status - a.status;
}
});
const unStatusData = data.filter((item) => !(item.status !== ''));
return [...StatusData, ...unStatusData];
}
const fakeData: Order[] = [
{ id: '728ed52f', amount: 1, status: 0, email: '[email protected]' },
{
id: 'e4b1c2a9',
amount: 3,
status: 4,
email: '[email protected]',
},
{ id: 'f9e8d7c6', amount: 3, status: 2, email: '[email protected]' },
{ id: '3b2a1c9d', amount: 9, status: '', email: '[email protected]' },
{ id: '7f8e9d2c', amount: 9, status: 3, email: '[email protected]' },
{ id: '6a5b4c3d', amount: 5, status: 2, email: '[email protected]' },
{
id: 'd5e6f7a8',
amount: 4,
status: '',
email: '[email protected]',
},
{ id: '9c8d7e6f', amount: 5, status: 1, email: '[email protected]' },
{ id: '2b3c4d5e', amount: 2, status: 3, email: '[email protected]' },
{ id: '1e2d3c4b', amount: 0, status: 0, email: '[email protected]' },
];
const columns: ColumnDef<Order>[] = [
{
accessorKey: 'status',
header: ({ column }) => <HeaderButton column={column}>Status</HeaderButton>,
// custom sorting logic ?
},
{
accessorKey: 'email',
header: ({ column }) => <HeaderButton column={column}>Email</HeaderButton>,
},
{
accessorKey: 'amount',
header: ({ column }) => <HeaderButton column={column}>Amount</HeaderButton>,
},
];
function HeaderButton({
column,
children,
}: {
column: Column<Order, unknown>;
children: React.ReactNode;
}) {
return (
<div
className="cursor-pointer flex gap-2 items-center"
onClick={() => {
if (column.getIsSorted() === 'asc') {
column.toggleSorting(true);
} else if (column.getIsSorted() === 'desc') {
column.clearSorting();
} else {
column.toggleSorting(false);
}
}}
>
{children}
{column.getIsSorted() === 'asc' && (
<ArrowDownNarrowWide className="w-4 h-4" />
)}
{column.getIsSorted() === 'desc' && (
<ArrowUpNarrowWide className="w-4 h-4" />
)}
{column.getIsSorted() === false && <ArrowDownUp className="w-4 h-4" />}
</div>
);
}
function DataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([]);
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
state: {
sorting: sorting,
},
});
return (
<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}
data-state={row.getIsSelected() && 'selected'}
>
{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>
);
}
export function DemoPage() {
const [direction, setDirection] = useState<'asc' | 'desc' | ''>('asc');
const [data, setData] = useState(fakeData);
// useEffect(() => {
// setData(getProcessedData(fakeData, direction));
// }, [direction]);
return (
<div className="container mx-auto py-10">
<Button
className="mb-2"
onClick={() => {
// setDirection((direction) => {
// if (direction === '') {
// return 'asc';
// } else if (direction === 'asc') {
// return 'desc';
// } else if (direction === 'desc') {
// return '';
// }
// });
}}
>
Sort Status
</Button>
<DataTable columns={columns} data={data} />
</div>
);
}
Upvotes: 0
Views: 428