@etsoo/react 1.7.32 → 1.7.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/__tests__/EventWatcher.tsx +2 -2
- package/__tests__/States.tsx +2 -2
- package/lib/components/ScrollerGrid.js +29 -29
- package/lib/components/ScrollerList.js +23 -22
- package/package.json +17 -17
- package/src/components/ScrollerGrid.tsx +31 -32
- package/src/components/ScrollerList.tsx +28 -23
package/LICENSE
CHANGED
|
@@ -21,7 +21,7 @@ test('Tests for EventWatcher', () => {
|
|
|
21
21
|
render(<App callback={callback} />);
|
|
22
22
|
const button = screen.getByRole<HTMLButtonElement>('button');
|
|
23
23
|
button.click();
|
|
24
|
-
expect(callback).
|
|
24
|
+
expect(callback).toHaveBeenCalled();
|
|
25
25
|
button.click();
|
|
26
|
-
expect(callback).
|
|
26
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
27
27
|
});
|
package/__tests__/States.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import { useAsyncState } from '../src/uses/useAsyncState';
|
|
4
|
-
import { act } from 'react
|
|
4
|
+
import { act } from 'react';
|
|
5
5
|
|
|
6
6
|
function App(props: { callback: (state: number) => void }) {
|
|
7
7
|
const { callback } = props;
|
|
@@ -31,7 +31,7 @@ test('Tests for useAsyncState', (done) => {
|
|
|
31
31
|
|
|
32
32
|
setTimeout(function () {
|
|
33
33
|
// Expect to happen
|
|
34
|
-
expect(callback).
|
|
34
|
+
expect(callback).toHaveBeenLastCalledWith(2);
|
|
35
35
|
|
|
36
36
|
// Notify jest to complete
|
|
37
37
|
done();
|
|
@@ -11,10 +11,10 @@ export const ScrollerGrid = (props) => {
|
|
|
11
11
|
// Rows
|
|
12
12
|
const [rows, updateRows] = React.useState([]);
|
|
13
13
|
const setRows = (rows, reset = false) => {
|
|
14
|
-
|
|
14
|
+
refs.current.loadedItems = rows.length;
|
|
15
15
|
updateRows(rows);
|
|
16
16
|
if (!reset && onUpdateRows)
|
|
17
|
-
onUpdateRows(rows,
|
|
17
|
+
onUpdateRows(rows, refs.current);
|
|
18
18
|
};
|
|
19
19
|
// Refs
|
|
20
20
|
const refs = React.useRef({
|
|
@@ -29,17 +29,16 @@ export const ScrollerGrid = (props) => {
|
|
|
29
29
|
selectedItems: [],
|
|
30
30
|
idCache: {}
|
|
31
31
|
});
|
|
32
|
-
const state = refs.current;
|
|
33
32
|
const ref = React.useRef(null);
|
|
34
33
|
// Load data
|
|
35
34
|
const loadDataLocal = (pageAdd = 1) => {
|
|
36
35
|
// Prevent multiple loadings
|
|
37
|
-
if (!
|
|
36
|
+
if (!refs.current.hasNextPage || refs.current.isNextPageLoading)
|
|
38
37
|
return;
|
|
39
38
|
// Update state
|
|
40
|
-
|
|
39
|
+
refs.current.isNextPageLoading = true;
|
|
41
40
|
// Parameters
|
|
42
|
-
const { currentPage, batchSize, orderBy, orderByAsc, data } =
|
|
41
|
+
const { currentPage, batchSize, orderBy, orderByAsc, data } = refs.current;
|
|
43
42
|
const loadProps = {
|
|
44
43
|
currentPage,
|
|
45
44
|
batchSize,
|
|
@@ -48,37 +47,37 @@ export const ScrollerGrid = (props) => {
|
|
|
48
47
|
data
|
|
49
48
|
};
|
|
50
49
|
loadData(loadProps).then((result) => {
|
|
51
|
-
if (result == null ||
|
|
50
|
+
if (result == null || refs.current.isMounted === false) {
|
|
52
51
|
return;
|
|
53
52
|
}
|
|
54
|
-
|
|
53
|
+
refs.current.isMounted = true;
|
|
55
54
|
const newItems = result.length;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
refs.current.lastLoadedItems = newItems;
|
|
56
|
+
refs.current.isNextPageLoading = false;
|
|
57
|
+
refs.current.hasNextPage = newItems >= batchSize;
|
|
59
58
|
if (pageAdd === 0) {
|
|
60
59
|
// New items
|
|
61
|
-
const newRows =
|
|
60
|
+
const newRows = refs.current.lastLoadedItems
|
|
62
61
|
? [...rows]
|
|
63
|
-
.splice(rows.length -
|
|
62
|
+
.splice(rows.length - refs.current.lastLoadedItems, refs.current.lastLoadedItems)
|
|
64
63
|
.concat(result)
|
|
65
64
|
: result;
|
|
66
|
-
|
|
65
|
+
refs.current.idCache = {};
|
|
67
66
|
for (const row of newRows) {
|
|
68
67
|
const id = row[idField];
|
|
69
|
-
|
|
68
|
+
refs.current.idCache[id] = null;
|
|
70
69
|
}
|
|
71
70
|
// Update rows
|
|
72
71
|
setRows(newRows);
|
|
73
72
|
}
|
|
74
73
|
else {
|
|
75
74
|
// Set current page
|
|
76
|
-
|
|
75
|
+
refs.current.currentPage = refs.current.currentPage + pageAdd;
|
|
77
76
|
// Update rows, avoid duplicate items
|
|
78
77
|
const newRows = [...rows];
|
|
79
78
|
for (const item of result) {
|
|
80
79
|
const id = item[idField];
|
|
81
|
-
if (
|
|
80
|
+
if (refs.current.idCache[id] === undefined) {
|
|
82
81
|
newRows.push(item);
|
|
83
82
|
}
|
|
84
83
|
}
|
|
@@ -128,9 +127,9 @@ export const ScrollerGrid = (props) => {
|
|
|
128
127
|
lastLoadedItems: undefined,
|
|
129
128
|
...add
|
|
130
129
|
};
|
|
131
|
-
Object.assign(
|
|
130
|
+
Object.assign(refs.current, resetState);
|
|
132
131
|
// Reset items
|
|
133
|
-
if (
|
|
132
|
+
if (refs.current.isMounted !== false)
|
|
134
133
|
setRows(items, true);
|
|
135
134
|
};
|
|
136
135
|
const instance = {
|
|
@@ -162,13 +161,13 @@ export const ScrollerGrid = (props) => {
|
|
|
162
161
|
},
|
|
163
162
|
select(rowIndex) {
|
|
164
163
|
// Select only one item
|
|
165
|
-
const selectedItems =
|
|
164
|
+
const selectedItems = refs.current.selectedItems;
|
|
166
165
|
selectedItems[0] = rows[rowIndex];
|
|
167
166
|
if (onSelectChange)
|
|
168
167
|
onSelectChange(selectedItems);
|
|
169
168
|
},
|
|
170
169
|
selectAll(checked) {
|
|
171
|
-
const selectedItems =
|
|
170
|
+
const selectedItems = refs.current.selectedItems;
|
|
172
171
|
rows.forEach((row) => {
|
|
173
172
|
const index = selectedItems.findIndex((selectedItem) => selectedItem[idField] === row[idField]);
|
|
174
173
|
if (checked) {
|
|
@@ -183,7 +182,7 @@ export const ScrollerGrid = (props) => {
|
|
|
183
182
|
onSelectChange(selectedItems);
|
|
184
183
|
},
|
|
185
184
|
selectItem(item, checked) {
|
|
186
|
-
const selectedItems =
|
|
185
|
+
const selectedItems = refs.current.selectedItems;
|
|
187
186
|
const index = selectedItems.findIndex((selectedItem) => selectedItem[idField] === item[idField]);
|
|
188
187
|
if (checked) {
|
|
189
188
|
if (index === -1)
|
|
@@ -210,7 +209,7 @@ export const ScrollerGrid = (props) => {
|
|
|
210
209
|
React.useImperativeHandle(mRef, () => instance, [rows]);
|
|
211
210
|
React.useEffect(() => {
|
|
212
211
|
return () => {
|
|
213
|
-
|
|
212
|
+
refs.current.isMounted = false;
|
|
214
213
|
};
|
|
215
214
|
}, []);
|
|
216
215
|
// Force update to work with the new width and rowHeight
|
|
@@ -224,9 +223,9 @@ export const ScrollerGrid = (props) => {
|
|
|
224
223
|
// Rows
|
|
225
224
|
const rowLength = rows.length;
|
|
226
225
|
// Row count
|
|
227
|
-
const rowCount =
|
|
226
|
+
const rowCount = refs.current.hasNextPage ? rowLength + 1 : rowLength;
|
|
228
227
|
// Auto load data when current page is 0
|
|
229
|
-
if (
|
|
228
|
+
if (refs.current.currentPage === 0 && refs.current.autoLoad) {
|
|
230
229
|
const initItems = onInitLoad == null ? undefined : onInitLoad(ref.current);
|
|
231
230
|
if (initItems)
|
|
232
231
|
reset(initItems[1], initItems[0]);
|
|
@@ -235,13 +234,14 @@ export const ScrollerGrid = (props) => {
|
|
|
235
234
|
}
|
|
236
235
|
// Layout
|
|
237
236
|
return (React.createElement(React.Fragment, null,
|
|
238
|
-
headerRenderer && headerRenderer(
|
|
237
|
+
headerRenderer && headerRenderer(refs.current),
|
|
239
238
|
React.createElement(VariableSizeGrid, { itemKey: ({ columnIndex, rowIndex, data }) => {
|
|
240
239
|
if (data == null)
|
|
241
240
|
return [rowIndex, columnIndex].join(',');
|
|
242
|
-
|
|
241
|
+
// ${data[idField]}-${rowIndex} always unique but no cache for the same item
|
|
242
|
+
return [`${data[idField]}`, columnIndex].join(',');
|
|
243
243
|
}, onItemsRendered: onItemsRenderedLocal, ref: ref, rowCount: rowCount, rowHeight: typeof rowHeight === 'function'
|
|
244
244
|
? rowHeight
|
|
245
|
-
: () => rowHeight, style: { overflowX: 'hidden' }, width: width, ...rest }, (props) => itemRendererLocal(props,
|
|
246
|
-
footerRenderer && footerRenderer(rows,
|
|
245
|
+
: () => rowHeight, style: { overflowX: 'hidden' }, width: width, ...rest }, (props) => itemRendererLocal(props, refs.current)),
|
|
246
|
+
footerRenderer && footerRenderer(rows, refs.current)));
|
|
247
247
|
};
|
|
@@ -29,10 +29,10 @@ export const ScrollerList = (props) => {
|
|
|
29
29
|
// Rows
|
|
30
30
|
const [rows, updateRows] = React.useState([]);
|
|
31
31
|
const setRows = (rows, reset = false) => {
|
|
32
|
-
|
|
32
|
+
stateRefs.current.loadedItems = rows.length;
|
|
33
33
|
updateRows(rows);
|
|
34
34
|
if (!reset && onUpdateRows)
|
|
35
|
-
onUpdateRows(rows,
|
|
35
|
+
onUpdateRows(rows, stateRefs.current);
|
|
36
36
|
};
|
|
37
37
|
// States
|
|
38
38
|
const batchSize = GridSizeGet(loadBatchSize, height);
|
|
@@ -48,16 +48,16 @@ export const ScrollerList = (props) => {
|
|
|
48
48
|
selectedItems: [],
|
|
49
49
|
idCache: {}
|
|
50
50
|
});
|
|
51
|
-
const state = stateRefs.current;
|
|
52
51
|
// Load data
|
|
53
52
|
const loadDataLocal = (pageAdd = 1) => {
|
|
54
53
|
// Prevent multiple loadings
|
|
55
|
-
if (!
|
|
54
|
+
if (!stateRefs.current.hasNextPage ||
|
|
55
|
+
stateRefs.current.isNextPageLoading)
|
|
56
56
|
return;
|
|
57
57
|
// Update state
|
|
58
|
-
|
|
58
|
+
stateRefs.current.isNextPageLoading = true;
|
|
59
59
|
// Parameters
|
|
60
|
-
const { currentPage, batchSize, orderBy, orderByAsc, data } =
|
|
60
|
+
const { currentPage, batchSize, orderBy, orderByAsc, data } = stateRefs.current;
|
|
61
61
|
const loadProps = {
|
|
62
62
|
currentPage,
|
|
63
63
|
batchSize,
|
|
@@ -66,36 +66,37 @@ export const ScrollerList = (props) => {
|
|
|
66
66
|
data
|
|
67
67
|
};
|
|
68
68
|
loadData(loadProps).then((result) => {
|
|
69
|
-
if (result == null ||
|
|
69
|
+
if (result == null || stateRefs.current.isMounted === false) {
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
|
-
|
|
72
|
+
stateRefs.current.isMounted = true;
|
|
73
73
|
const newItems = result.length;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
stateRefs.current.lastLoadedItems = newItems;
|
|
75
|
+
stateRefs.current.hasNextPage = newItems >= batchSize;
|
|
76
|
+
stateRefs.current.isNextPageLoading = false;
|
|
77
77
|
if (pageAdd === 0) {
|
|
78
78
|
// New items
|
|
79
|
-
const newRows =
|
|
79
|
+
const newRows = stateRefs.current.lastLoadedItems
|
|
80
80
|
? [...rows]
|
|
81
|
-
.splice(rows.length -
|
|
81
|
+
.splice(rows.length - stateRefs.current.lastLoadedItems, stateRefs.current.lastLoadedItems)
|
|
82
82
|
.concat(result)
|
|
83
83
|
: result;
|
|
84
|
-
|
|
84
|
+
stateRefs.current.idCache = {};
|
|
85
85
|
for (const row of newRows) {
|
|
86
86
|
const id = row[idField];
|
|
87
|
-
|
|
87
|
+
stateRefs.current.idCache[id] = null;
|
|
88
88
|
}
|
|
89
89
|
// Update rows
|
|
90
90
|
setRows(newRows);
|
|
91
91
|
}
|
|
92
92
|
else {
|
|
93
|
-
|
|
93
|
+
stateRefs.current.currentPage =
|
|
94
|
+
stateRefs.current.currentPage + pageAdd;
|
|
94
95
|
// Update rows, avoid duplicate items
|
|
95
96
|
const newRows = [...rows];
|
|
96
97
|
for (const item of result) {
|
|
97
98
|
const id = item[idField];
|
|
98
|
-
if (
|
|
99
|
+
if (stateRefs.current.idCache[id] === undefined) {
|
|
99
100
|
newRows.push(item);
|
|
100
101
|
}
|
|
101
102
|
}
|
|
@@ -121,9 +122,9 @@ export const ScrollerList = (props) => {
|
|
|
121
122
|
isNextPageLoading: false,
|
|
122
123
|
...add
|
|
123
124
|
};
|
|
124
|
-
Object.assign(
|
|
125
|
+
Object.assign(stateRefs.current, resetState);
|
|
125
126
|
// Reset
|
|
126
|
-
if (
|
|
127
|
+
if (stateRefs.current.isMounted !== false)
|
|
127
128
|
setRows(items, true);
|
|
128
129
|
};
|
|
129
130
|
React.useImperativeHandle(mRef, () => {
|
|
@@ -159,7 +160,7 @@ export const ScrollerList = (props) => {
|
|
|
159
160
|
React.useEffect(() => {
|
|
160
161
|
// Return clear function
|
|
161
162
|
return () => {
|
|
162
|
-
|
|
163
|
+
stateRefs.current.isMounted = false;
|
|
163
164
|
};
|
|
164
165
|
}, []);
|
|
165
166
|
// Row count
|
|
@@ -176,9 +177,9 @@ export const ScrollerList = (props) => {
|
|
|
176
177
|
onItemsRendered(props);
|
|
177
178
|
};
|
|
178
179
|
// Item count
|
|
179
|
-
const itemCount =
|
|
180
|
+
const itemCount = stateRefs.current.hasNextPage ? rowCount + 1 : rowCount;
|
|
180
181
|
// Auto load data when current page is 0
|
|
181
|
-
if (
|
|
182
|
+
if (stateRefs.current.currentPage === 0 && stateRefs.current.autoLoad) {
|
|
182
183
|
const initItems = onInitLoad == null ? undefined : onInitLoad(listRef.current);
|
|
183
184
|
if (initItems)
|
|
184
185
|
reset(initItems[1], initItems[0]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etsoo/react",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.34",
|
|
4
4
|
"description": "TypeScript ReactJs UI Independent Framework",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -49,30 +49,30 @@
|
|
|
49
49
|
"@dnd-kit/sortable": "^8.0.0",
|
|
50
50
|
"@emotion/css": "^11.11.2",
|
|
51
51
|
"@emotion/react": "^11.11.4",
|
|
52
|
-
"@emotion/styled": "^11.11.
|
|
53
|
-
"@etsoo/appscript": "^1.4.
|
|
54
|
-
"@etsoo/notificationbase": "^1.1.
|
|
55
|
-
"@etsoo/shared": "^1.2.
|
|
56
|
-
"react": "^18.
|
|
57
|
-
"react-dom": "^18.
|
|
58
|
-
"react-router-dom": "^6.
|
|
52
|
+
"@emotion/styled": "^11.11.5",
|
|
53
|
+
"@etsoo/appscript": "^1.4.84",
|
|
54
|
+
"@etsoo/notificationbase": "^1.1.42",
|
|
55
|
+
"@etsoo/shared": "^1.2.37",
|
|
56
|
+
"react": "^18.3.1",
|
|
57
|
+
"react-dom": "^18.3.1",
|
|
58
|
+
"react-router-dom": "^6.23.0",
|
|
59
59
|
"react-window": "^1.8.10"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@babel/cli": "^7.
|
|
63
|
-
"@babel/core": "^7.24.
|
|
64
|
-
"@babel/plugin-transform-runtime": "^7.24.
|
|
65
|
-
"@babel/preset-env": "^7.24.
|
|
66
|
-
"@babel/runtime-corejs3": "^7.24.
|
|
62
|
+
"@babel/cli": "^7.24.1",
|
|
63
|
+
"@babel/core": "^7.24.4",
|
|
64
|
+
"@babel/plugin-transform-runtime": "^7.24.3",
|
|
65
|
+
"@babel/preset-env": "^7.24.4",
|
|
66
|
+
"@babel/runtime-corejs3": "^7.24.4",
|
|
67
67
|
"@testing-library/jest-dom": "^6.4.2",
|
|
68
|
-
"@testing-library/react": "^
|
|
68
|
+
"@testing-library/react": "^15.0.5",
|
|
69
69
|
"@types/jest": "^29.5.12",
|
|
70
|
-
"@types/react": "^18.
|
|
71
|
-
"@types/react-dom": "^18.
|
|
70
|
+
"@types/react": "^18.3.1",
|
|
71
|
+
"@types/react-dom": "^18.3.0",
|
|
72
72
|
"@types/react-window": "^1.8.8",
|
|
73
73
|
"jest": "^29.7.0",
|
|
74
74
|
"jest-environment-jsdom": "^29.7.0",
|
|
75
75
|
"ts-jest": "^29.1.2",
|
|
76
|
-
"typescript": "^5.
|
|
76
|
+
"typescript": "^5.4.5"
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -182,10 +182,10 @@ export const ScrollerGrid = <T extends object>(props: ScrollerGridProps<T>) => {
|
|
|
182
182
|
// Rows
|
|
183
183
|
const [rows, updateRows] = React.useState<T[]>([]);
|
|
184
184
|
const setRows = (rows: T[], reset: boolean = false) => {
|
|
185
|
-
|
|
185
|
+
refs.current.loadedItems = rows.length;
|
|
186
186
|
updateRows(rows);
|
|
187
187
|
|
|
188
|
-
if (!reset && onUpdateRows) onUpdateRows(rows,
|
|
188
|
+
if (!reset && onUpdateRows) onUpdateRows(rows, refs.current);
|
|
189
189
|
};
|
|
190
190
|
|
|
191
191
|
// Refs
|
|
@@ -201,20 +201,20 @@ export const ScrollerGrid = <T extends object>(props: ScrollerGridProps<T>) => {
|
|
|
201
201
|
selectedItems: [],
|
|
202
202
|
idCache: {}
|
|
203
203
|
});
|
|
204
|
-
const state = refs.current;
|
|
205
204
|
|
|
206
205
|
const ref = React.useRef<VariableSizeGrid<T>>(null);
|
|
207
206
|
|
|
208
207
|
// Load data
|
|
209
208
|
const loadDataLocal = (pageAdd: number = 1) => {
|
|
210
209
|
// Prevent multiple loadings
|
|
211
|
-
if (!
|
|
210
|
+
if (!refs.current.hasNextPage || refs.current.isNextPageLoading) return;
|
|
212
211
|
|
|
213
212
|
// Update state
|
|
214
|
-
|
|
213
|
+
refs.current.isNextPageLoading = true;
|
|
215
214
|
|
|
216
215
|
// Parameters
|
|
217
|
-
const { currentPage, batchSize, orderBy, orderByAsc, data } =
|
|
216
|
+
const { currentPage, batchSize, orderBy, orderByAsc, data } =
|
|
217
|
+
refs.current;
|
|
218
218
|
|
|
219
219
|
const loadProps: GridLoadDataProps = {
|
|
220
220
|
currentPage,
|
|
@@ -225,45 +225,45 @@ export const ScrollerGrid = <T extends object>(props: ScrollerGridProps<T>) => {
|
|
|
225
225
|
};
|
|
226
226
|
|
|
227
227
|
loadData(loadProps).then((result) => {
|
|
228
|
-
if (result == null ||
|
|
228
|
+
if (result == null || refs.current.isMounted === false) {
|
|
229
229
|
return;
|
|
230
230
|
}
|
|
231
|
-
|
|
231
|
+
refs.current.isMounted = true;
|
|
232
232
|
|
|
233
233
|
const newItems = result.length;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
234
|
+
refs.current.lastLoadedItems = newItems;
|
|
235
|
+
refs.current.isNextPageLoading = false;
|
|
236
|
+
refs.current.hasNextPage = newItems >= batchSize;
|
|
237
237
|
|
|
238
238
|
if (pageAdd === 0) {
|
|
239
239
|
// New items
|
|
240
|
-
const newRows =
|
|
240
|
+
const newRows = refs.current.lastLoadedItems
|
|
241
241
|
? [...rows]
|
|
242
242
|
.splice(
|
|
243
|
-
rows.length -
|
|
244
|
-
|
|
243
|
+
rows.length - refs.current.lastLoadedItems,
|
|
244
|
+
refs.current.lastLoadedItems
|
|
245
245
|
)
|
|
246
246
|
.concat(result)
|
|
247
247
|
: result;
|
|
248
248
|
|
|
249
|
-
|
|
249
|
+
refs.current.idCache = {};
|
|
250
250
|
for (const row of newRows) {
|
|
251
251
|
const id = row[idField] as any;
|
|
252
|
-
|
|
252
|
+
refs.current.idCache[id] = null;
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
// Update rows
|
|
256
256
|
setRows(newRows);
|
|
257
257
|
} else {
|
|
258
258
|
// Set current page
|
|
259
|
-
|
|
259
|
+
refs.current.currentPage = refs.current.currentPage + pageAdd;
|
|
260
260
|
|
|
261
261
|
// Update rows, avoid duplicate items
|
|
262
262
|
const newRows = [...rows];
|
|
263
263
|
|
|
264
264
|
for (const item of result) {
|
|
265
265
|
const id = item[idField] as any;
|
|
266
|
-
if (
|
|
266
|
+
if (refs.current.idCache[id] === undefined) {
|
|
267
267
|
newRows.push(item);
|
|
268
268
|
}
|
|
269
269
|
}
|
|
@@ -327,10 +327,10 @@ export const ScrollerGrid = <T extends object>(props: ScrollerGridProps<T>) => {
|
|
|
327
327
|
lastLoadedItems: undefined,
|
|
328
328
|
...add
|
|
329
329
|
};
|
|
330
|
-
Object.assign(
|
|
330
|
+
Object.assign(refs.current, resetState);
|
|
331
331
|
|
|
332
332
|
// Reset items
|
|
333
|
-
if (
|
|
333
|
+
if (refs.current.isMounted !== false) setRows(items, true);
|
|
334
334
|
};
|
|
335
335
|
|
|
336
336
|
const instance: ScrollerGridForwardRef<T> = {
|
|
@@ -367,13 +367,13 @@ export const ScrollerGrid = <T extends object>(props: ScrollerGridProps<T>) => {
|
|
|
367
367
|
},
|
|
368
368
|
select(rowIndex: number) {
|
|
369
369
|
// Select only one item
|
|
370
|
-
const selectedItems =
|
|
370
|
+
const selectedItems = refs.current.selectedItems;
|
|
371
371
|
selectedItems[0] = rows[rowIndex];
|
|
372
372
|
|
|
373
373
|
if (onSelectChange) onSelectChange(selectedItems);
|
|
374
374
|
},
|
|
375
375
|
selectAll(checked: boolean) {
|
|
376
|
-
const selectedItems =
|
|
376
|
+
const selectedItems = refs.current.selectedItems;
|
|
377
377
|
|
|
378
378
|
rows.forEach((row) => {
|
|
379
379
|
const index = selectedItems.findIndex(
|
|
@@ -390,7 +390,7 @@ export const ScrollerGrid = <T extends object>(props: ScrollerGridProps<T>) => {
|
|
|
390
390
|
if (onSelectChange) onSelectChange(selectedItems);
|
|
391
391
|
},
|
|
392
392
|
selectItem(item: T, checked: boolean) {
|
|
393
|
-
const selectedItems =
|
|
393
|
+
const selectedItems = refs.current.selectedItems;
|
|
394
394
|
const index = selectedItems.findIndex(
|
|
395
395
|
(selectedItem) => selectedItem[idField] === item[idField]
|
|
396
396
|
);
|
|
@@ -423,7 +423,7 @@ export const ScrollerGrid = <T extends object>(props: ScrollerGridProps<T>) => {
|
|
|
423
423
|
|
|
424
424
|
React.useEffect(() => {
|
|
425
425
|
return () => {
|
|
426
|
-
|
|
426
|
+
refs.current.isMounted = false;
|
|
427
427
|
};
|
|
428
428
|
}, []);
|
|
429
429
|
|
|
@@ -440,10 +440,10 @@ export const ScrollerGrid = <T extends object>(props: ScrollerGridProps<T>) => {
|
|
|
440
440
|
const rowLength = rows.length;
|
|
441
441
|
|
|
442
442
|
// Row count
|
|
443
|
-
const rowCount =
|
|
443
|
+
const rowCount = refs.current.hasNextPage ? rowLength + 1 : rowLength;
|
|
444
444
|
|
|
445
445
|
// Auto load data when current page is 0
|
|
446
|
-
if (
|
|
446
|
+
if (refs.current.currentPage === 0 && refs.current.autoLoad) {
|
|
447
447
|
const initItems =
|
|
448
448
|
onInitLoad == null ? undefined : onInitLoad(ref.current);
|
|
449
449
|
if (initItems) reset(initItems[1], initItems[0]);
|
|
@@ -453,13 +453,12 @@ export const ScrollerGrid = <T extends object>(props: ScrollerGridProps<T>) => {
|
|
|
453
453
|
// Layout
|
|
454
454
|
return (
|
|
455
455
|
<React.Fragment>
|
|
456
|
-
{headerRenderer && headerRenderer(
|
|
456
|
+
{headerRenderer && headerRenderer(refs.current)}
|
|
457
457
|
<VariableSizeGrid<T>
|
|
458
458
|
itemKey={({ columnIndex, rowIndex, data }) => {
|
|
459
459
|
if (data == null) return [rowIndex, columnIndex].join(',');
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
);
|
|
460
|
+
// ${data[idField]}-${rowIndex} always unique but no cache for the same item
|
|
461
|
+
return [`${data[idField]}`, columnIndex].join(',');
|
|
463
462
|
}}
|
|
464
463
|
onItemsRendered={onItemsRenderedLocal}
|
|
465
464
|
ref={ref}
|
|
@@ -473,9 +472,9 @@ export const ScrollerGrid = <T extends object>(props: ScrollerGridProps<T>) => {
|
|
|
473
472
|
width={width}
|
|
474
473
|
{...rest}
|
|
475
474
|
>
|
|
476
|
-
{(props) => itemRendererLocal(props,
|
|
475
|
+
{(props) => itemRendererLocal(props, refs.current)}
|
|
477
476
|
</VariableSizeGrid>
|
|
478
|
-
{footerRenderer && footerRenderer(rows,
|
|
477
|
+
{footerRenderer && footerRenderer(rows, refs.current)}
|
|
479
478
|
</React.Fragment>
|
|
480
479
|
);
|
|
481
480
|
};
|
|
@@ -145,10 +145,10 @@ export const ScrollerList = <T extends object>(props: ScrollerListProps<T>) => {
|
|
|
145
145
|
// Rows
|
|
146
146
|
const [rows, updateRows] = React.useState<T[]>([]);
|
|
147
147
|
const setRows = (rows: T[], reset: boolean = false) => {
|
|
148
|
-
|
|
148
|
+
stateRefs.current.loadedItems = rows.length;
|
|
149
149
|
updateRows(rows);
|
|
150
150
|
|
|
151
|
-
if (!reset && onUpdateRows) onUpdateRows(rows,
|
|
151
|
+
if (!reset && onUpdateRows) onUpdateRows(rows, stateRefs.current);
|
|
152
152
|
};
|
|
153
153
|
|
|
154
154
|
// States
|
|
@@ -165,18 +165,22 @@ export const ScrollerList = <T extends object>(props: ScrollerListProps<T>) => {
|
|
|
165
165
|
selectedItems: [],
|
|
166
166
|
idCache: {}
|
|
167
167
|
});
|
|
168
|
-
const state = stateRefs.current;
|
|
169
168
|
|
|
170
169
|
// Load data
|
|
171
170
|
const loadDataLocal = (pageAdd: number = 1) => {
|
|
172
171
|
// Prevent multiple loadings
|
|
173
|
-
if (
|
|
172
|
+
if (
|
|
173
|
+
!stateRefs.current.hasNextPage ||
|
|
174
|
+
stateRefs.current.isNextPageLoading
|
|
175
|
+
)
|
|
176
|
+
return;
|
|
174
177
|
|
|
175
178
|
// Update state
|
|
176
|
-
|
|
179
|
+
stateRefs.current.isNextPageLoading = true;
|
|
177
180
|
|
|
178
181
|
// Parameters
|
|
179
|
-
const { currentPage, batchSize, orderBy, orderByAsc, data } =
|
|
182
|
+
const { currentPage, batchSize, orderBy, orderByAsc, data } =
|
|
183
|
+
stateRefs.current;
|
|
180
184
|
|
|
181
185
|
const loadProps: GridLoadDataProps = {
|
|
182
186
|
currentPage,
|
|
@@ -187,44 +191,45 @@ export const ScrollerList = <T extends object>(props: ScrollerListProps<T>) => {
|
|
|
187
191
|
};
|
|
188
192
|
|
|
189
193
|
loadData(loadProps).then((result) => {
|
|
190
|
-
if (result == null ||
|
|
194
|
+
if (result == null || stateRefs.current.isMounted === false) {
|
|
191
195
|
return;
|
|
192
196
|
}
|
|
193
|
-
|
|
197
|
+
stateRefs.current.isMounted = true;
|
|
194
198
|
|
|
195
199
|
const newItems = result.length;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
200
|
+
stateRefs.current.lastLoadedItems = newItems;
|
|
201
|
+
stateRefs.current.hasNextPage = newItems >= batchSize;
|
|
202
|
+
stateRefs.current.isNextPageLoading = false;
|
|
199
203
|
|
|
200
204
|
if (pageAdd === 0) {
|
|
201
205
|
// New items
|
|
202
|
-
const newRows =
|
|
206
|
+
const newRows = stateRefs.current.lastLoadedItems
|
|
203
207
|
? [...rows]
|
|
204
208
|
.splice(
|
|
205
|
-
rows.length -
|
|
206
|
-
|
|
209
|
+
rows.length - stateRefs.current.lastLoadedItems,
|
|
210
|
+
stateRefs.current.lastLoadedItems
|
|
207
211
|
)
|
|
208
212
|
.concat(result)
|
|
209
213
|
: result;
|
|
210
214
|
|
|
211
|
-
|
|
215
|
+
stateRefs.current.idCache = {};
|
|
212
216
|
for (const row of newRows) {
|
|
213
217
|
const id = row[idField] as any;
|
|
214
|
-
|
|
218
|
+
stateRefs.current.idCache[id] = null;
|
|
215
219
|
}
|
|
216
220
|
|
|
217
221
|
// Update rows
|
|
218
222
|
setRows(newRows);
|
|
219
223
|
} else {
|
|
220
|
-
|
|
224
|
+
stateRefs.current.currentPage =
|
|
225
|
+
stateRefs.current.currentPage + pageAdd;
|
|
221
226
|
|
|
222
227
|
// Update rows, avoid duplicate items
|
|
223
228
|
const newRows = [...rows];
|
|
224
229
|
|
|
225
230
|
for (const item of result) {
|
|
226
231
|
const id = item[idField] as any;
|
|
227
|
-
if (
|
|
232
|
+
if (stateRefs.current.idCache[id] === undefined) {
|
|
228
233
|
newRows.push(item);
|
|
229
234
|
}
|
|
230
235
|
}
|
|
@@ -253,10 +258,10 @@ export const ScrollerList = <T extends object>(props: ScrollerListProps<T>) => {
|
|
|
253
258
|
isNextPageLoading: false,
|
|
254
259
|
...add
|
|
255
260
|
};
|
|
256
|
-
Object.assign(
|
|
261
|
+
Object.assign(stateRefs.current, resetState);
|
|
257
262
|
|
|
258
263
|
// Reset
|
|
259
|
-
if (
|
|
264
|
+
if (stateRefs.current.isMounted !== false) setRows(items, true);
|
|
260
265
|
};
|
|
261
266
|
|
|
262
267
|
React.useImperativeHandle(
|
|
@@ -301,7 +306,7 @@ export const ScrollerList = <T extends object>(props: ScrollerListProps<T>) => {
|
|
|
301
306
|
React.useEffect(() => {
|
|
302
307
|
// Return clear function
|
|
303
308
|
return () => {
|
|
304
|
-
|
|
309
|
+
stateRefs.current.isMounted = false;
|
|
305
310
|
};
|
|
306
311
|
}, []);
|
|
307
312
|
|
|
@@ -322,10 +327,10 @@ export const ScrollerList = <T extends object>(props: ScrollerListProps<T>) => {
|
|
|
322
327
|
};
|
|
323
328
|
|
|
324
329
|
// Item count
|
|
325
|
-
const itemCount =
|
|
330
|
+
const itemCount = stateRefs.current.hasNextPage ? rowCount + 1 : rowCount;
|
|
326
331
|
|
|
327
332
|
// Auto load data when current page is 0
|
|
328
|
-
if (
|
|
333
|
+
if (stateRefs.current.currentPage === 0 && stateRefs.current.autoLoad) {
|
|
329
334
|
const initItems =
|
|
330
335
|
onInitLoad == null ? undefined : onInitLoad(listRef.current);
|
|
331
336
|
if (initItems) reset(initItems[1], initItems[0]);
|