src/hooks/useTableSort.ts (81 lines of code) (raw):

import { useState, useMemo } from 'react'; import useRawSearchParams from './useRawSearchParams'; import type { CompareResultsItem } from '../types/state'; import type { CompareResultsTableConfig } from '../types/types'; // This hook handles the state that handles table sorting, and also takes care // of handling the URL parameters that mirror this state. // // In the URL: // * no column indication means the default (that is there is no sort) // * otherwise, a single sort parameter contains both the column and the // direction, separated by a character |. const useTableSort = (columnsConfiguration: CompareResultsTableConfig) => { // This is our custom hook that updates the search params without a rerender. const [rawSearchParams, updateRawSearchParams] = useRawSearchParams(); const sortFromUrl = rawSearchParams.get('sort') ?? ''; const [columnId, direction] = useMemo(() => { const [columnId, direction] = sortFromUrl.split('|'); if (!columnId) { return [null, null]; } const columnConfiguration = columnsConfiguration.find( (column) => column.key === columnId, ); if (!columnConfiguration) { console.warn(`The column ${columnId} is unknown, reseting the sort.`); return [null, null]; } if (direction !== 'asc' && direction !== 'desc') { return [columnId, 'desc']; } return [columnId, direction]; }, [sortFromUrl, columnsConfiguration]); const [sortDirection, setSortDirection] = useState( direction as 'asc' | 'desc' | null, ); const [sortColumn, setSortColumn] = useState(columnId); const onToggleSort = ( columnId: string, newSortDirection: 'asc' | 'desc' | null, ) => { if (newSortDirection === null) { setSortColumn(null); setSortDirection(null); rawSearchParams.delete('sort'); } else { setSortColumn(columnId); setSortDirection(newSortDirection); rawSearchParams.set('sort', columnId + '|' + newSortDirection); } updateRawSearchParams(rawSearchParams); }; return { sortDirection, sortColumn, onToggleSort }; }; export default useTableSort; // This function sorts the results array in accordance to the specified column // and direction. If no column is specified, the first column (the subtests) // is used. export function sortResults( columnsConfiguration: CompareResultsTableConfig, results: CompareResultsItem[], columnId: string | null, direction: 'asc' | 'desc' | null, defaultSortFunction: ( resultA: CompareResultsItem, resultB: CompareResultsItem, ) => number, ) { let sortFunction = defaultSortFunction; let columnConfiguration: CompareResultsTableConfig[number] | undefined; if (columnId && direction) { columnConfiguration = columnsConfiguration.find( (column) => column.key === columnId, ); } if (columnConfiguration) { if (!('sortFunction' in columnConfiguration)) { console.warn( `No sortFunction information for the columnConfiguration ${String( columnConfiguration.name ?? columnId, )}`, ); return results; } sortFunction = columnConfiguration.sortFunction; } const directionedSortFunction = direction === 'desc' ? (itemA: CompareResultsItem, itemB: CompareResultsItem) => sortFunction(itemB, itemA) : sortFunction; return results.toSorted(directionedSortFunction); }