Skip to content

Commit a3821d8

Browse files
authored
refactor: rewrite sorting state in typescript and add tests (#19589)
* refactor: rewrite sorting state in typescript and add tests * fix: update table sort types given DataTable rewrite
1 parent 2db0d08 commit a3821d8

File tree

7 files changed

+156
-115
lines changed

7 files changed

+156
-115
lines changed

packages/react/src/components/DataTable/DataTable.tsx

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import React, {
1717
} from 'react';
1818
import isEqual from 'react-fast-compare';
1919
import getDerivedStateFromProps from './state/getDerivedStateFromProps';
20-
import { getNextSortState } from './state/sorting';
20+
import { getNextSortState, type SortRowFn } from './state/sorting';
2121
import type { DataTableSortState } from './state/sortStates';
2222
import { getCellId } from './tools/cells';
2323
import denormalize from './tools/denormalize';
@@ -260,21 +260,7 @@ export interface DataTableProps<RowType, ColTypes extends any[]>
260260
) => ReactElement;
261261
rows: Omit<DataTableRow<ColTypes>, 'cells'>[];
262262
size?: DataTableSize;
263-
sortRow?: (
264-
cellA: any,
265-
cellB: any,
266-
options: {
267-
sortDirection: DataTableSortState;
268-
sortStates: Record<DataTableSortState, DataTableSortState>;
269-
locale: string;
270-
key: string;
271-
compare: (
272-
a: number | string,
273-
b: number | string,
274-
locale?: string
275-
) => number;
276-
}
277-
) => number;
263+
sortRow?: SortRowFn;
278264
stickyHeader?: boolean;
279265
useStaticWidth?: boolean;
280266
useZebraStyles?: boolean;

packages/react/src/components/DataTable/TableHeader.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@ import {
1919
ArrowsVertical as Arrows,
2020
} from '@carbon/icons-react';
2121
import classNames from 'classnames';
22-
import { sortStates } from './state/sorting';
2322
import { useId } from '../../internal/useId';
2423
import { usePrefix } from '../../internal/usePrefix';
2524
import { TranslateWithId } from '../../types/common';
26-
import { DataTableSortState } from './state/sortStates';
25+
import { sortStates, type DataTableSortState } from './state/sortStates';
2726
import { AILabel } from '../AILabel';
2827
import { isComponentElement } from '../../internal';
2928

packages/react/src/components/DataTable/state/__tests__/sorting-test.js

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2023
2+
* Copyright IBM Corp. 2016, 2025
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
import { sortStates } from '../sortStates';
9+
810
describe('sorting state', () => {
911
let sorting;
10-
let sortStates;
1112
let initialSortState;
1213
let getNextSortDirection;
1314
let getNextSortState;
@@ -18,7 +19,6 @@ describe('sorting state', () => {
1819
}));
1920

2021
sorting = require('../sorting');
21-
sortStates = sorting.sortStates;
2222
initialSortState = sorting.initialSortState;
2323
getNextSortDirection = sorting.getNextSortDirection;
2424
getNextSortState = sorting.getNextSortState;
@@ -190,5 +190,29 @@ describe('sorting state', () => {
190190
rowIds: ['a', 'b', 'c'],
191191
});
192192
});
193+
194+
it('should sort without `sortRow` being provided', () => {
195+
const state = getNextSortState({ locale: 'en' }, mockState, { key: 'a' });
196+
197+
expect(state.rowIds).toEqual(['b', 'a', 'c']);
198+
});
199+
200+
it('should reset sort direction when a different header is sorted', () => {
201+
const state1 = getNextSortState(mockProps, mockState, { key: 'a' });
202+
const state2 = getNextSortState(
203+
mockProps,
204+
{ ...mockState, ...state1 },
205+
{ key: 'b' }
206+
);
207+
208+
expect(state2.sortDirection).toBe(sortStates.ASC);
209+
});
210+
211+
it('should handle empty `cellsById`', () => {
212+
const emptyState = { ...mockState, cellsById: {} };
213+
const state = getNextSortState(mockProps, emptyState, { key: 'a' });
214+
215+
expect(state.rowIds).toEqual(['b', 'a', 'c']);
216+
});
193217
});
194218
});

packages/react/src/components/DataTable/state/sorting.js

Lines changed: 0 additions & 93 deletions
This file was deleted.
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/**
2+
* Copyright IBM Corp. 2016, 2025
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import { sortStates, type DataTableSortState } from './sortStates';
9+
import { sortRows } from '../tools/sorting';
10+
import type { DataTableCell } from '../DataTable';
11+
12+
export interface SortRowParams {
13+
key: string;
14+
sortDirection: DataTableSortState;
15+
sortStates: Record<DataTableSortState, DataTableSortState>;
16+
locale: string;
17+
compare: (a: string | number, b: string | number, locale?: string) => number;
18+
}
19+
20+
export type SortRowFn = (
21+
cellA: any,
22+
cellB: any,
23+
options: SortRowParams
24+
) => number;
25+
26+
interface Props {
27+
locale?: string;
28+
sortRow?: SortRowFn;
29+
}
30+
31+
interface State<ColTypes extends any[]> {
32+
rowIds: string[];
33+
cellsById: Record<string, DataTableCell<ColTypes[number]>>;
34+
initialRowOrder: string[];
35+
sortHeaderKey: string | null;
36+
sortDirection: DataTableSortState;
37+
}
38+
39+
export const initialSortState = sortStates.NONE;
40+
41+
/**
42+
* Gets the next sort direction for a header.
43+
*
44+
* @param prevHeader - Key of the previously sorted header.
45+
* @param currentHeader - Key of the currently selected header.
46+
* @param prevState - Previous sort direction.
47+
*/
48+
export const getNextSortDirection = (
49+
prevHeader: string,
50+
currentHeader: string,
51+
prevState: DataTableSortState
52+
): DataTableSortState => {
53+
// Cycle for sorting the same header: NONE -> ASC -> DESC -> NONE.
54+
if (prevHeader === currentHeader) {
55+
switch (prevState) {
56+
case sortStates.NONE:
57+
return sortStates.ASC;
58+
case sortStates.ASC:
59+
return sortStates.DESC;
60+
case sortStates.DESC:
61+
return sortStates.NONE;
62+
}
63+
}
64+
65+
// Sorting a new header starts at ascending order.
66+
return sortStates.ASC;
67+
};
68+
69+
/**
70+
* Gets the next sort state.
71+
*
72+
* @param props - Component props.
73+
* @param state - Current table state.
74+
* @param key - Header key to sort by.
75+
*/
76+
export const getNextSortState = <ColTypes extends any[]>(
77+
props: Props,
78+
state: State<ColTypes>,
79+
{ key }: { key: string }
80+
): Pick<State<ColTypes>, 'sortHeaderKey' | 'sortDirection' | 'rowIds'> => {
81+
const { sortDirection, sortHeaderKey } = state;
82+
83+
const nextSortDirection = getNextSortDirection(
84+
key,
85+
sortHeaderKey ?? '',
86+
sortDirection
87+
);
88+
89+
return getSortedState(props, state, key, nextSortDirection);
90+
};
91+
92+
/**
93+
* Gets a sort state update.
94+
*
95+
* @param props - Component props.
96+
* @param state - Current state of the table.
97+
* @param key - Header key to sort by.
98+
* @param sortDirection - Sort direction to apply.
99+
*/
100+
export const getSortedState = <ColTypes extends any[]>(
101+
{ locale, sortRow }: Props,
102+
{ rowIds, cellsById, initialRowOrder }: State<ColTypes>,
103+
key: string,
104+
sortDirection: DataTableSortState
105+
): Pick<State<ColTypes>, 'rowIds' | 'sortDirection' | 'sortHeaderKey'> => {
106+
const nextRowIds =
107+
sortDirection !== sortStates.NONE
108+
? sortRows({
109+
rowIds,
110+
cellsById,
111+
sortDirection,
112+
key,
113+
locale,
114+
sortRow,
115+
})
116+
: initialRowOrder;
117+
118+
return {
119+
sortHeaderKey: key,
120+
sortDirection,
121+
rowIds: nextRowIds,
122+
};
123+
};

packages/react/src/components/DataTable/tools/__tests__/sorting-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
import { compare, defaultSortRow, sortRows } from '../sorting';
9-
import { sortStates } from '../../state/sorting';
9+
import { sortStates } from '../../state/sortStates';
1010

1111
describe('sortRows', () => {
1212
const rowIds = ['row2', 'row1'];

packages/react/src/components/DataTable/tools/sorting.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ interface Cell {
5252
value: any;
5353
}
5454

55+
// TODO: Should `SortRowParams` in
56+
// packages/react/src/components/DataTable/state/sorting.ts be used here?
5557
interface SortRowParams {
5658
key: string;
5759
sortDirection: DataTableSortState;

0 commit comments

Comments
 (0)