Skip to content

Commit 9fb5f66

Browse files
committed
chore: refactor paginated tables
1 parent 410d4d4 commit 9fb5f66

14 files changed

+985
-364
lines changed

src/components/PaginatedTable/PaginatedTable.tsx

+48-33
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import React from 'react';
22

3-
import {TableWithControlsLayout} from '../TableWithControlsLayout/TableWithControlsLayout';
4-
3+
import {usePaginatedTableState} from './PaginatedTableContext';
54
import {TableChunk} from './TableChunk';
65
import {TableHead} from './TableHead';
76
import {DEFAULT_TABLE_ROW_HEIGHT} from './constants';
@@ -12,7 +11,6 @@ import type {
1211
GetRowClassName,
1312
HandleTableColumnsResize,
1413
PaginatedTableData,
15-
RenderControls,
1614
RenderEmptyDataMessage,
1715
RenderErrorMessage,
1816
SortParams,
@@ -31,9 +29,9 @@ export interface PaginatedTableProps<T, F> {
3129
getRowClassName?: GetRowClassName<T>;
3230
rowHeight?: number;
3331
parentRef: React.RefObject<HTMLElement>;
32+
tableContainerRef: React.RefObject<HTMLDivElement>;
3433
initialSortParams?: SortParams;
3534
onColumnsResize?: HandleTableColumnsResize;
36-
renderControls?: RenderControls;
3735
renderEmptyDataMessage?: RenderEmptyDataMessage;
3836
renderErrorMessage?: RenderErrorMessage;
3937
containerClassName?: string;
@@ -53,22 +51,38 @@ export const PaginatedTable = <T, F>({
5351
getRowClassName,
5452
rowHeight = DEFAULT_TABLE_ROW_HEIGHT,
5553
parentRef,
54+
tableContainerRef,
5655
initialSortParams,
5756
onColumnsResize,
58-
renderControls,
5957
renderErrorMessage,
6058
renderEmptyDataMessage,
6159
containerClassName,
6260
onDataFetched,
6361
keepCache = true,
6462
}: PaginatedTableProps<T, F>) => {
65-
const initialTotal = initialEntitiesCount || 0;
66-
const initialFound = initialEntitiesCount || 1;
63+
// Get state and setters from context
64+
const {tableState, setSortParams, setTotalEntities, setFoundEntities, setIsInitialLoad} =
65+
usePaginatedTableState();
66+
67+
const {sortParams, foundEntities} = tableState;
6768

68-
const [sortParams, setSortParams] = React.useState<SortParams | undefined>(initialSortParams);
69-
const [totalEntities, setTotalEntities] = React.useState(initialTotal);
70-
const [foundEntities, setFoundEntities] = React.useState(initialFound);
71-
const [isInitialLoad, setIsInitialLoad] = React.useState(true);
69+
// Initialize state with props if available
70+
React.useEffect(() => {
71+
if (initialSortParams) {
72+
setSortParams(initialSortParams);
73+
}
74+
75+
if (initialEntitiesCount) {
76+
setTotalEntities(initialEntitiesCount);
77+
setFoundEntities(initialEntitiesCount);
78+
}
79+
}, [
80+
setSortParams,
81+
setTotalEntities,
82+
setFoundEntities,
83+
initialSortParams,
84+
initialEntitiesCount,
85+
]);
7286

7387
const tableRef = React.useRef<HTMLDivElement>(null);
7488

@@ -105,18 +119,34 @@ export const PaginatedTable = <T, F>({
105119
onDataFetched?.(data);
106120
}
107121
},
108-
[onDataFetched],
122+
[onDataFetched, setFoundEntities, setIsInitialLoad, setTotalEntities],
109123
);
110124

111125
// reset table on filters change
112126
React.useLayoutEffect(() => {
113-
setTotalEntities(initialTotal);
114-
setFoundEntities(initialFound);
127+
const defaultTotal = initialEntitiesCount || 0;
128+
const defaultFound = initialEntitiesCount || 1;
129+
130+
setTotalEntities(defaultTotal);
131+
setFoundEntities(defaultFound);
115132
setIsInitialLoad(true);
116-
if (parentRef?.current) {
117-
parentRef.current.scrollTo(0, 0);
133+
134+
if (tableContainerRef.current && parentRef.current) {
135+
// Scroll the parent container to the position of the table container
136+
const tableRect = tableContainerRef.current.getBoundingClientRect();
137+
const parentRect = parentRef.current.getBoundingClientRect();
138+
const scrollTop = tableRect.top - parentRect.top + parentRef.current.scrollTop;
139+
parentRef.current.scrollTo(0, scrollTop);
118140
}
119-
}, [rawFilters, initialFound, initialTotal, parentRef]);
141+
}, [
142+
rawFilters,
143+
initialEntitiesCount,
144+
tableContainerRef,
145+
setTotalEntities,
146+
setFoundEntities,
147+
setIsInitialLoad,
148+
parentRef,
149+
]);
120150

121151
const renderChunks = () => {
122152
return activeChunks.map((isActive, index) => (
@@ -148,24 +178,9 @@ export const PaginatedTable = <T, F>({
148178
</table>
149179
);
150180

151-
const renderContent = () => {
152-
if (renderControls) {
153-
return (
154-
<TableWithControlsLayout>
155-
<TableWithControlsLayout.Controls>
156-
{renderControls({inited: !isInitialLoad, totalEntities, foundEntities})}
157-
</TableWithControlsLayout.Controls>
158-
<TableWithControlsLayout.Table>{renderTable()}</TableWithControlsLayout.Table>
159-
</TableWithControlsLayout>
160-
);
161-
}
162-
163-
return renderTable();
164-
};
165-
166181
return (
167182
<div ref={tableRef} className={b(null, containerClassName)}>
168-
{renderContent()}
183+
{renderTable()}
169184
</div>
170185
);
171186
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import React from 'react';
2+
3+
import type {PaginatedTableState} from './types';
4+
5+
// Default state for the table
6+
const defaultTableState: PaginatedTableState = {
7+
sortParams: undefined,
8+
totalEntities: 0,
9+
foundEntities: 0,
10+
isInitialLoad: true,
11+
};
12+
13+
// Context type definition
14+
interface PaginatedTableStateContextType {
15+
// State
16+
tableState: PaginatedTableState;
17+
18+
// Granular setters
19+
setSortParams: (params: PaginatedTableState['sortParams']) => void;
20+
setTotalEntities: (total: number) => void;
21+
setFoundEntities: (found: number) => void;
22+
setIsInitialLoad: (isInitial: boolean) => void;
23+
}
24+
25+
// Creating the context with default values
26+
export const PaginatedTableStateContext = React.createContext<PaginatedTableStateContextType>({
27+
tableState: defaultTableState,
28+
setSortParams: () => undefined,
29+
setTotalEntities: () => undefined,
30+
setFoundEntities: () => undefined,
31+
setIsInitialLoad: () => undefined,
32+
});
33+
34+
// Provider component props
35+
interface PaginatedTableStateProviderProps {
36+
children: React.ReactNode;
37+
initialState?: Partial<PaginatedTableState>;
38+
}
39+
40+
// Provider component
41+
export const PaginatedTableProvider = ({
42+
children,
43+
initialState = {},
44+
}: PaginatedTableStateProviderProps) => {
45+
// Use individual state variables for each field
46+
const [sortParams, setSortParams] = React.useState<PaginatedTableState['sortParams']>(
47+
initialState.sortParams ?? defaultTableState.sortParams,
48+
);
49+
const [totalEntities, setTotalEntities] = React.useState<number>(
50+
initialState.totalEntities ?? defaultTableState.totalEntities,
51+
);
52+
const [foundEntities, setFoundEntities] = React.useState<number>(
53+
initialState.foundEntities ?? defaultTableState.foundEntities,
54+
);
55+
const [isInitialLoad, setIsInitialLoad] = React.useState<boolean>(
56+
initialState.isInitialLoad ?? defaultTableState.isInitialLoad,
57+
);
58+
59+
// Construct tableState from individual state variables
60+
const tableState = React.useMemo(
61+
() => ({
62+
sortParams,
63+
totalEntities,
64+
foundEntities,
65+
isInitialLoad,
66+
}),
67+
[sortParams, totalEntities, foundEntities, isInitialLoad],
68+
);
69+
70+
// Create the context value with the constructed tableState and direct setters
71+
const contextValue = React.useMemo(
72+
() => ({
73+
tableState,
74+
setSortParams,
75+
setTotalEntities,
76+
setFoundEntities,
77+
setIsInitialLoad,
78+
}),
79+
[tableState, setSortParams, setTotalEntities, setFoundEntities, setIsInitialLoad],
80+
);
81+
82+
return (
83+
<PaginatedTableStateContext.Provider value={contextValue}>
84+
{children}
85+
</PaginatedTableStateContext.Provider>
86+
);
87+
};
88+
89+
// Custom hook for consuming the context
90+
export const usePaginatedTableState = () => {
91+
const context = React.useContext(PaginatedTableStateContext);
92+
93+
if (context === undefined) {
94+
throw new Error('usePaginatedTableState must be used within a PaginatedTableStateProvider');
95+
}
96+
97+
return context;
98+
};

src/components/PaginatedTable/ResizeablePaginatedTable.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {useTableResize} from '../../utils/hooks/useTableResize';
55
import type {PaginatedTableProps} from './PaginatedTable';
66
import {PaginatedTable} from './PaginatedTable';
77
import {b} from './shared';
8-
import type {Column} from './types';
8+
import type {Column, PaginatedTableState} from './types';
99

1010
function updateColumnsWidth<T>(columns: Column<T>[], columnsWidthSetup: ColumnWidthByName) {
1111
return columns.map((column) => {
@@ -16,11 +16,13 @@ function updateColumnsWidth<T>(columns: Column<T>[], columnsWidthSetup: ColumnWi
1616
interface ResizeablePaginatedTableProps<T, F>
1717
extends Omit<PaginatedTableProps<T, F>, 'onColumnsResize'> {
1818
columnsWidthLSKey: string;
19+
onStateChange?: (state: PaginatedTableState) => void;
1920
}
2021

2122
export function ResizeablePaginatedTable<T, F>({
2223
columnsWidthLSKey,
2324
columns,
25+
tableContainerRef,
2426
...props
2527
}: ResizeablePaginatedTableProps<T, F>) {
2628
const [tableColumnsWidth, setTableColumnsWidth] = useTableResize(columnsWidthLSKey);
@@ -29,6 +31,7 @@ export function ResizeablePaginatedTable<T, F>({
2931

3032
return (
3133
<PaginatedTable
34+
tableContainerRef={tableContainerRef}
3235
columns={updatedColumns}
3336
onColumnsResize={setTableColumnsWidth}
3437
containerClassName={b('resizeable-table-container')}

src/components/PaginatedTable/types.ts

+7
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ export type FetchData<T, F = undefined, E = {}> = (
5858

5959
export type OnError = (error?: IResponseError) => void;
6060

61+
export interface PaginatedTableState {
62+
sortParams?: SortParams;
63+
totalEntities: number;
64+
foundEntities: number;
65+
isInitialLoad: boolean;
66+
}
67+
6168
interface ControlsParams {
6269
totalEntities: number;
6370
foundEntities: number;

src/components/TableWithControlsLayout/TableWithControlsLayout.tsx

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import React from 'react';
2+
13
import {cn} from '../../utils/cn';
24
import {TableSkeleton} from '../TableSkeleton/TableSkeleton';
35

@@ -32,10 +34,17 @@ TableWithControlsLayout.Controls = function TableControls({
3234
);
3335
};
3436

35-
TableWithControlsLayout.Table = function Table({children, loading, className}: TableProps) {
37+
TableWithControlsLayout.Table = React.forwardRef<HTMLDivElement, TableProps>(function Table(
38+
{children, loading, className},
39+
ref,
40+
) {
3641
if (loading) {
3742
return <TableSkeleton className={b('loader')} />;
3843
}
3944

40-
return <div className={b('table', className)}>{children}</div>;
41-
};
45+
return (
46+
<div ref={ref} className={b('table', className)}>
47+
{children}
48+
</div>
49+
);
50+
});

0 commit comments

Comments
 (0)