Skip to content

Commit 62fd913

Browse files
committed
Made cell flushing async in general and only keep it sync while scrolling. release version patch
1 parent 602c045 commit 62fd913

File tree

11 files changed

+285
-23
lines changed

11 files changed

+285
-23
lines changed
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import * as React from 'react';
2+
3+
import {
4+
InfiniteTable,
5+
DataSource,
6+
InfiniteTablePropColumns,
7+
DataSourceApi,
8+
InfiniteTableApi,
9+
DataSourceDataFn,
10+
InfiniteTableProps,
11+
} from '@infinite-table/infinite-react';
12+
import { Button } from '@/components/ui/button';
13+
14+
type Developer = {
15+
id: number;
16+
firstName: string;
17+
lastName: string;
18+
country: string;
19+
city: string;
20+
currency: string;
21+
preferredLanguage: string;
22+
stack: string;
23+
canDesign: 'yes' | 'no';
24+
hobby: string;
25+
salary: number;
26+
age: number;
27+
};
28+
29+
const columns: InfiniteTablePropColumns<Developer> = {
30+
// id: { field: 'id' },
31+
salary: { field: 'salary' },
32+
age: { field: 'age' },
33+
firstName: { field: 'firstName' },
34+
// preferredLanguage: { field: 'preferredLanguage' },
35+
// lastName: { field: 'lastName' },
36+
// country: { field: 'country' },
37+
// city: { field: 'city' },
38+
// currency: { field: 'currency' },
39+
// stack: { field: 'stack' },
40+
// canDesign: { field: 'canDesign' },
41+
};
42+
43+
const dataSourceFn: DataSourceDataFn<Developer> = ({}) => {
44+
return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql`)
45+
.then((r) => r.json())
46+
.then((data) => {
47+
return new Promise((resolve) => {
48+
setTimeout(() => {
49+
// console.log(data, 'data');
50+
// data.length = 1;
51+
// const newData = [...data.data.slice(0, 3)];
52+
const newData = data;
53+
54+
resolve(newData);
55+
}, 20);
56+
});
57+
});
58+
};
59+
60+
export default function DataTestPage() {
61+
const [active, setActive] = React.useState([true, true]);
62+
const [ds, setDs] = React.useState<DataSourceDataFn<Developer>>(
63+
() => dataSourceFn,
64+
);
65+
const [dataSourceApi, setDataSourceApi] =
66+
React.useState<DataSourceApi<Developer>>();
67+
const [activeCellIndex, setActiveCellIndex] = React.useState<
68+
[number, number] | null
69+
>(null);
70+
const [api, setApi] = React.useState<InfiniteTableApi<Developer>>();
71+
const [header, setHeader] = React.useState<boolean>(false);
72+
73+
const onReady = React.useCallback(
74+
({
75+
dataSourceApi,
76+
api,
77+
}: {
78+
dataSourceApi: DataSourceApi<Developer>;
79+
api: InfiniteTableApi<Developer>;
80+
}) => {
81+
setDataSourceApi(dataSourceApi);
82+
setApi(api);
83+
},
84+
[],
85+
);
86+
87+
const domProps: InfiniteTableProps<Developer>['domProps'] =
88+
React.useMemo(() => {
89+
return {
90+
style: {
91+
margin: '5px',
92+
height: 900,
93+
border: '1px solid gray',
94+
position: 'relative',
95+
},
96+
};
97+
}, []);
98+
return (
99+
<React.StrictMode>
100+
<div className="flex gap-2 items-center justify-center m-1">
101+
<Button
102+
onClick={() => {
103+
if (!dataSourceApi || !api) {
104+
return;
105+
}
106+
107+
const { start, end } = api.getVisibleRenderRange();
108+
const [startRow, startCol] = start;
109+
const [endRow, endCol] = end;
110+
// console.log(start, end);
111+
112+
const randomRow =
113+
Math.floor(Math.random() * (endRow - startRow + 1)) + startRow;
114+
const randomCol =
115+
Math.floor(Math.random() * (endCol - startCol + 1)) + startCol;
116+
117+
const [activeRow, activeCol] = activeCellIndex || [];
118+
119+
const updateRow = activeRow ?? randomRow;
120+
const updateCol = activeCol ?? randomCol;
121+
const colId = Object.keys(columns)[updateCol];
122+
const rowId = dataSourceApi.getPrimaryKeyByIndex(updateRow);
123+
dataSourceApi.updateData({
124+
[colId]: Math.floor(Math.random() * 10000),
125+
id: rowId,
126+
});
127+
128+
// dataSourceApi.get
129+
// dataSourceApi.;
130+
}}
131+
>
132+
update
133+
</Button>
134+
<Button
135+
onClick={() => {
136+
setDs(dataSourceFn.bind(null));
137+
}}
138+
>
139+
update datasource
140+
</Button>
141+
<Button
142+
onClick={() => {
143+
setHeader((header) => !header);
144+
}}
145+
>
146+
toggle header
147+
</Button>
148+
</div>
149+
{active[0] && (
150+
<DataSource<Developer> data={ds} primaryKey="id">
151+
<InfiniteTable<Developer>
152+
header={header}
153+
onActiveCellIndexChange={setActiveCellIndex}
154+
debugId="test"
155+
onReady={onReady}
156+
domProps={domProps}
157+
columns={columns}
158+
/>
159+
</DataSource>
160+
)}
161+
</React.StrictMode>
162+
);
163+
}

source/src/components/DataSource/types.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,15 @@ export type DataSourceRemoteData<T> = {
124124
livePaginationCursor?: DataSourceLivePaginationCursorValue;
125125
};
126126

127+
export type DataSourceDataFn<T> = (
128+
dataInfo: DataSourceDataParams<T>,
129+
) => T[] | Promise<T[] | DataSourceRemoteData<T>>;
130+
127131
export type DataSourceData<T> =
128132
| T[]
129133
| DataSourceRemoteData<T>
130134
| Promise<T[] | DataSourceRemoteData<T>>
131-
| ((
132-
dataInfo: DataSourceDataParams<T>,
133-
) => T[] | Promise<T[] | DataSourceRemoteData<T>>);
135+
| DataSourceDataFn<T>;
134136

135137
export type DataSourceGroupRowsList<KeyType = any> = true | KeyType[][];
136138

source/src/components/HeadlessTable/GridCellInterface.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import { Renderable } from '../types/Renderable';
22

33
export interface GridCellInterface<T_ADDITIONAL_CELL_INFO = any> {
44
debugId: string;
5-
update(content: Renderable, additionalInfo?: T_ADDITIONAL_CELL_INFO): void;
5+
update(
6+
content: Renderable,
7+
additionalInfo?: T_ADDITIONAL_CELL_INFO,
8+
scrollingObjectParam?: {
9+
scrolling: boolean;
10+
},
11+
): void;
612
getElement(): HTMLElement | null;
713
getNode(): Renderable;
814
destroy(): void;

source/src/components/HeadlessTable/GridCellManager.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,9 @@ export class GridCellManager<T_ADDITIONAL_CELL_INFO> extends Logger {
185185
cell: GridCellInterface<T_ADDITIONAL_CELL_INFO>,
186186
cellPos: CellPos,
187187
additionalInfo?: T_ADDITIONAL_CELL_INFO,
188+
scrollingObjectParam?: {
189+
scrolling: boolean;
190+
},
188191
) {
189192
const currentCellAtPos = this.getCellAt(cellPos);
190193

@@ -199,7 +202,7 @@ export class GridCellManager<T_ADDITIONAL_CELL_INFO> extends Logger {
199202

200203
this.setCellPositionInMatrix(cell, cellPos);
201204

202-
cell.update(node, additionalInfo);
205+
cell.update(node, additionalInfo, scrollingObjectParam);
203206

204207
return cell;
205208
}

source/src/components/HeadlessTable/GridCellPoolForReact.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ class GridCellForReact<T_ADDITIONAL_CELL_INFO = any>
2626

2727
private mounted: boolean = false;
2828

29+
private IS_UPDATED_WHILE_SCROLLING = false;
30+
2931
private mountSubscription =
3032
buildSubscriptionCallback<GridCellInterface<T_ADDITIONAL_CELL_INFO>>();
3133

@@ -39,7 +41,14 @@ class GridCellForReact<T_ADDITIONAL_CELL_INFO = any>
3941

4042
this.updater = buildSubscriptionCallback<Renderable>();
4143

42-
this.node = <AvoidReactDiff key={key} name={key} updater={this.updater} />;
44+
this.node = (
45+
<AvoidReactDiff
46+
key={key}
47+
name={key}
48+
updater={this.updater}
49+
shouldFlushSync={this.isUpdatedWhileScrolling}
50+
/>
51+
);
4352

4453
this.ref = (htmlElement) => {
4554
this.element = htmlElement;
@@ -52,6 +61,10 @@ class GridCellForReact<T_ADDITIONAL_CELL_INFO = any>
5261
};
5362
}
5463

64+
isUpdatedWhileScrolling = () => {
65+
return this.IS_UPDATED_WHILE_SCROLLING;
66+
};
67+
5568
isMounted() {
5669
return this.mounted;
5770
}
@@ -79,7 +92,16 @@ class GridCellForReact<T_ADDITIONAL_CELL_INFO = any>
7992
return this.node;
8093
}
8194

82-
update(content: Renderable, additionalInfo?: T_ADDITIONAL_CELL_INFO): void {
95+
update(
96+
content: Renderable,
97+
additionalInfo?: T_ADDITIONAL_CELL_INFO,
98+
scrollingObjectParam?: {
99+
scrolling: boolean;
100+
},
101+
): void {
102+
this.IS_UPDATED_WHILE_SCROLLING = scrollingObjectParam
103+
? scrollingObjectParam.scrolling
104+
: false;
83105
this.updater(content);
84106
this.cellInfo = additionalInfo;
85107
}

source/src/components/HeadlessTable/ReactHeadlessTableRenderer.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ export const columnOffsetAtIndexWhileReordering = stripVar(
4242
InternalVars.columnOffsetAtIndexWhileReordering,
4343
);
4444

45+
const SCROLLING_OBJECT_PARAM_FLYWEIGHT = {
46+
scrolling: false,
47+
};
48+
4549
export class GridRenderer extends Logger {
4650
protected brain: MatrixBrain;
4751

@@ -1364,11 +1368,13 @@ export class GridRenderer extends Logger {
13641368

13651369
return;
13661370
}
1371+
SCROLLING_OBJECT_PARAM_FLYWEIGHT.scrolling = this.scrolling;
13671372
this.cellManager.renderNodeAtCell(
13681373
renderedNode,
13691374
cell,
13701375
[rowIndex, colIndex],
13711376
cellAdditionalInfo,
1377+
SCROLLING_OBJECT_PARAM_FLYWEIGHT,
13721378
);
13731379

13741380
this.updateElementPosition(cell, { hidden, rowspan, colspan });

source/src/components/InfiniteTable/api/getImperativeApi.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ class InfiniteTableApiImpl<T> implements InfiniteTableApi<T> {
121121
this.getState().scrollerDOMRef.current?.focus();
122122
}
123123

124+
getVisibleRenderRange = () => {
125+
return this.getState().brain.getRenderRange();
126+
};
127+
124128
hideContextMenu() {
125129
this.actions.contextMenuVisibleFor = null;
126130
this.actions.cellContextMenuVisibleFor = null;

source/src/components/InfiniteTable/types/InfiniteTableProps.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ export interface InfiniteTableApi<T> {
295295
realignColumnContextMenu: () => void;
296296
getColumnOrder: () => string[];
297297
getVisibleColumnOrder: () => string[];
298+
getVisibleRenderRange: () => TableRenderRange;
298299
getComputedColumnById: (
299300
colId: string,
300301
) => InfiniteTableComputedColumn<T> | undefined;

source/src/components/RawList/AvoidReactDiff.tsx

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@ export type AvoidReactDiffProps = {
99
name?: string;
1010
useraf?: boolean;
1111
updater: SubscriptionCallback<Renderable>;
12+
shouldFlushSync?: () => boolean;
1213
};
1314

15+
const SHOULD_FLUSH_SYNC = () => false;
16+
1417
function AvoidReactDiffFn(props: AvoidReactDiffProps) {
1518
const [children, setChildren] = useState<Renderable>(props.updater.get);
1619

1720
const rafId = useRef<any>(null);
1821

22+
// const shouldFlushSync = SHOULD_FLUSH_SYNC;
23+
const shouldFlushSync = props.shouldFlushSync ?? SHOULD_FLUSH_SYNC;
24+
1925
// prev react 19 we had useEffect here
2026
// but there are situations when the updater would be called
2127
// after initial render and before useEffect triggered
@@ -26,23 +32,33 @@ function AvoidReactDiffFn(props: AvoidReactDiffProps) {
2632
// so when updater triggers a change
2733
// we can re-render and set the children
2834

35+
let FLUSH_SYNC = shouldFlushSync();
36+
2937
if (props.useraf) {
3038
if (rafId.current != null) {
3139
cancelAnimationFrame(rafId.current);
3240
}
3341
rafId.current = requestAnimationFrame(() => {
42+
if (FLUSH_SYNC) {
43+
queueMicrotask(() => {
44+
flushSync(() => {
45+
setChildren(children);
46+
});
47+
});
48+
} else {
49+
setChildren(children);
50+
}
51+
});
52+
} else {
53+
if (FLUSH_SYNC) {
3454
queueMicrotask(() => {
3555
flushSync(() => {
3656
setChildren(children);
3757
});
3858
});
39-
});
40-
} else {
41-
queueMicrotask(() => {
42-
flushSync(() => {
43-
setChildren(children);
44-
});
45-
});
59+
} else {
60+
setChildren(children);
61+
}
4662
}
4763
}
4864
const remove = props.updater.onChange(onChange);
@@ -53,7 +69,7 @@ function AvoidReactDiffFn(props: AvoidReactDiffProps) {
5369
}
5470
remove();
5571
};
56-
}, [props.updater, props.useraf]);
72+
}, [props.updater, props.useraf, shouldFlushSync]);
5773

5874
return (children as React.ReactNode) ?? null;
5975
}

0 commit comments

Comments
 (0)