@peerbit/react 0.0.20 → 0.0.22
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/lib/esm/useCount.d.ts +1 -1
- package/lib/esm/useCount.js +4 -1
- package/lib/esm/useCount.js.map +1 -1
- package/lib/esm/useProgram.js +25 -15
- package/lib/esm/useProgram.js.map +1 -1
- package/lib/esm/useQuery.d.ts +14 -13
- package/lib/esm/useQuery.js +166 -131
- package/lib/esm/useQuery.js.map +1 -1
- package/package.json +2 -2
- package/src/useCount.tsx +5 -2
- package/src/useProgram.tsx +38 -23
- package/src/useQuery.tsx +234 -213
package/src/useQuery.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from "react";
|
|
1
|
+
import { useState, useEffect, useRef, useReducer } from "react";
|
|
2
2
|
import {
|
|
3
3
|
ClosedError,
|
|
4
4
|
Documents,
|
|
@@ -8,26 +8,19 @@ import {
|
|
|
8
8
|
} from "@peerbit/document";
|
|
9
9
|
import * as indexerTypes from "@peerbit/indexer-interface";
|
|
10
10
|
import { AbortError } from "@peerbit/time";
|
|
11
|
+
import { NoPeersError } from "@peerbit/shared-log";
|
|
12
|
+
import { v4 as uuid } from "uuid";
|
|
11
13
|
|
|
14
|
+
/* ────────────── helper types ────────────── */
|
|
12
15
|
type QueryLike = {
|
|
13
16
|
query?: indexerTypes.Query[] | indexerTypes.QueryLike;
|
|
14
17
|
sort?: indexerTypes.Sort[] | indexerTypes.Sort | indexerTypes.SortLike;
|
|
15
18
|
};
|
|
16
|
-
type QueryOptions = { query: QueryLike; id
|
|
19
|
+
type QueryOptions = { query: QueryLike; id?: string };
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
options: { debug?: boolean | { id: string } } | undefined,
|
|
20
|
-
...args: any[]
|
|
21
|
-
) => {
|
|
22
|
-
if (!options?.debug) return;
|
|
23
|
-
|
|
24
|
-
if (typeof options.debug === "boolean") {
|
|
25
|
-
console.log(...args);
|
|
26
|
-
} else if (typeof options.debug.id === "string") {
|
|
27
|
-
console.log(options.debug.id, ...args);
|
|
28
|
-
}
|
|
29
|
-
};
|
|
21
|
+
type WaitForReplicatorsOption = { warmup?: number; eager?: boolean };
|
|
30
22
|
|
|
23
|
+
/* ────────────── main hook ────────────── */
|
|
31
24
|
export const useQuery = <
|
|
32
25
|
T extends Record<string, any>,
|
|
33
26
|
I extends Record<string, any>,
|
|
@@ -37,167 +30,179 @@ export const useQuery = <
|
|
|
37
30
|
db?: Documents<T, I>,
|
|
38
31
|
options?: {
|
|
39
32
|
resolve?: R;
|
|
40
|
-
|
|
41
|
-
transform?: (result: WithContext<RT>) => Promise<WithContext<RT>>;
|
|
33
|
+
transform?: (r: RT) => Promise<RT>;
|
|
42
34
|
debounce?: number;
|
|
43
35
|
debug?: boolean | { id: string };
|
|
44
36
|
reverse?: boolean;
|
|
45
|
-
batchSize?: number;
|
|
37
|
+
batchSize?: number;
|
|
38
|
+
prefetch?: boolean;
|
|
46
39
|
onChange?: {
|
|
47
40
|
merge?:
|
|
48
41
|
| boolean
|
|
49
42
|
| ((
|
|
50
|
-
|
|
43
|
+
c: DocumentsChange<T>
|
|
51
44
|
) =>
|
|
52
45
|
| DocumentsChange<T>
|
|
53
46
|
| Promise<DocumentsChange<T>>
|
|
54
|
-
| undefined);
|
|
55
|
-
update?: (
|
|
56
|
-
prev: WithContext<RT>[],
|
|
57
|
-
change: DocumentsChange<T>
|
|
58
|
-
) => WithContext<RT>[];
|
|
47
|
+
| undefined);
|
|
48
|
+
update?: (prev: RT[], change: DocumentsChange<T>) => RT[];
|
|
59
49
|
};
|
|
60
|
-
local?: boolean;
|
|
61
|
-
remote?:
|
|
62
|
-
| boolean
|
|
63
|
-
| {
|
|
64
|
-
eager?: boolean;
|
|
65
|
-
};
|
|
50
|
+
local?: boolean;
|
|
51
|
+
remote?: boolean | WaitForReplicatorsOption;
|
|
66
52
|
} & QueryOptions
|
|
67
53
|
) => {
|
|
68
|
-
|
|
69
|
-
|
|
54
|
+
/* ── «Item» is the concrete element type flowing through the hook ── */
|
|
55
|
+
type Item = RT;
|
|
56
|
+
|
|
57
|
+
/* ────────────── state & refs ────────────── */
|
|
58
|
+
const [all, setAll] = useState<Item[]>([]);
|
|
59
|
+
const allRef = useRef<Item[]>([]);
|
|
70
60
|
const [isLoading, setIsLoading] = useState(false);
|
|
71
|
-
const loadingMoreRef = useRef(false);
|
|
61
|
+
const loadingMoreRef = useRef<boolean>(false);
|
|
72
62
|
const iteratorRef = useRef<{
|
|
73
63
|
id?: string;
|
|
74
|
-
iterator: ResultsIterator<
|
|
64
|
+
iterator: ResultsIterator<Item>;
|
|
65
|
+
itemsConsumed: number;
|
|
75
66
|
} | null>(null);
|
|
76
67
|
const emptyResultsRef = useRef(false);
|
|
77
68
|
const closeControllerRef = useRef<AbortController | null>(null);
|
|
69
|
+
const waitedOnceRef = useRef(false);
|
|
70
|
+
const resetResultsOnReset = useRef(true);
|
|
78
71
|
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
fromChange?: DocumentsChange<any> | null
|
|
82
|
-
) => {
|
|
83
|
-
logWithId(
|
|
84
|
-
options,
|
|
85
|
-
"Loading more items, new combined length",
|
|
86
|
-
combined.length,
|
|
87
|
-
"from change",
|
|
88
|
-
fromChange
|
|
89
|
-
);
|
|
72
|
+
const [id, setId] = useState<string | undefined>(undefined);
|
|
73
|
+
const [resetCounter, invokeReset] = useReducer((n) => n + 1, 0);
|
|
90
74
|
|
|
91
|
-
|
|
75
|
+
const reverseRef = useRef(options?.reverse);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
reverseRef.current = options?.reverse;
|
|
78
|
+
}, [options?.reverse]);
|
|
79
|
+
|
|
80
|
+
/* ────────────── util ────────────── */
|
|
81
|
+
const log = (...a: any[]) => {
|
|
82
|
+
if (!options?.debug) return;
|
|
83
|
+
if (typeof options.debug === "boolean") console.log(...a);
|
|
84
|
+
else console.log(options.debug.id, ...a);
|
|
85
|
+
};
|
|
92
86
|
|
|
87
|
+
const updateAll = (combined: Item[]) => {
|
|
88
|
+
allRef.current = combined;
|
|
93
89
|
setAll(combined);
|
|
94
90
|
};
|
|
95
91
|
|
|
96
92
|
const reset = (
|
|
97
93
|
fromRef: {
|
|
98
94
|
id?: string;
|
|
99
|
-
iterator: ResultsIterator<
|
|
95
|
+
iterator: ResultsIterator<Item>;
|
|
100
96
|
} | null
|
|
101
97
|
) => {
|
|
102
|
-
|
|
98
|
+
const toClose = iteratorRef.current;
|
|
99
|
+
if (toClose && fromRef && toClose !== fromRef) {
|
|
103
100
|
return;
|
|
104
101
|
}
|
|
102
|
+
|
|
103
|
+
iteratorRef.current = null;
|
|
104
|
+
|
|
105
105
|
closeControllerRef.current?.abort();
|
|
106
106
|
closeControllerRef.current = new AbortController();
|
|
107
|
-
|
|
108
107
|
emptyResultsRef.current = false;
|
|
109
|
-
logWithId(options, "reset", {
|
|
110
|
-
id: iteratorRef.current?.id,
|
|
111
|
-
size: allRef.current.length,
|
|
112
|
-
});
|
|
113
108
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
109
|
+
toClose?.iterator.close();
|
|
110
|
+
|
|
111
|
+
if (resetResultsOnReset.current) {
|
|
112
|
+
allRef.current = [];
|
|
113
|
+
setAll([]);
|
|
114
|
+
}
|
|
115
|
+
|
|
118
116
|
setIsLoading(false);
|
|
119
117
|
loadingMoreRef.current = false;
|
|
120
|
-
|
|
118
|
+
log(options, "Iterator reset", toClose?.id, fromRef?.id);
|
|
119
|
+
setId(undefined);
|
|
121
120
|
};
|
|
122
121
|
|
|
123
|
-
// Initialize the iterator only once or when query changes
|
|
124
122
|
useEffect(() => {
|
|
125
|
-
|
|
123
|
+
resetResultsOnReset.current = true;
|
|
124
|
+
waitedOnceRef.current = false;
|
|
125
|
+
}, [db, options?.id ?? options?.query, options?.resolve, options?.reverse]);
|
|
126
|
+
|
|
127
|
+
/* ────────────── effect: (re)create iterator ────────────── */
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (!db || db.closed || options?.query == null) {
|
|
126
130
|
reset(null);
|
|
127
131
|
return;
|
|
128
132
|
}
|
|
129
133
|
|
|
130
134
|
const initIterator = () => {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
loadMore(); // initial load
|
|
151
|
-
return ref;
|
|
152
|
-
} catch (error) {
|
|
153
|
-
console.error("Error initializing iterator", error);
|
|
154
|
-
return null;
|
|
135
|
+
let id = options?.id ?? uuid();
|
|
136
|
+
const ref = {
|
|
137
|
+
id,
|
|
138
|
+
iterator: db.index.iterate(options.query ?? {}, {
|
|
139
|
+
local: options?.local ?? true,
|
|
140
|
+
remote:
|
|
141
|
+
options.remote == null || options.remote === false
|
|
142
|
+
? false
|
|
143
|
+
: typeof options.remote === "object"
|
|
144
|
+
? options.remote
|
|
145
|
+
: true,
|
|
146
|
+
resolve: options?.resolve,
|
|
147
|
+
}) as ResultsIterator<Item>,
|
|
148
|
+
itemsConsumed: 0,
|
|
149
|
+
};
|
|
150
|
+
iteratorRef.current = ref;
|
|
151
|
+
if (options?.prefetch) {
|
|
152
|
+
loadMore();
|
|
155
153
|
}
|
|
154
|
+
setId(id);
|
|
155
|
+
|
|
156
|
+
log("Iterator initialised", ref.id);
|
|
157
|
+
return ref;
|
|
156
158
|
};
|
|
157
159
|
|
|
158
|
-
// Reset state when the db or query changes.
|
|
159
160
|
reset(iteratorRef.current);
|
|
160
|
-
|
|
161
161
|
const newIteratorRef = initIterator();
|
|
162
162
|
|
|
163
|
+
/* live-merge listener (optional) */
|
|
163
164
|
let handleChange:
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
|
|
167
|
-
if (options?.onChange && options
|
|
168
|
-
|
|
165
|
+
| ((e: CustomEvent<DocumentsChange<T>>) => void | Promise<void>)
|
|
166
|
+
| undefined;
|
|
167
|
+
|
|
168
|
+
if (options?.onChange && options.onChange.merge !== false) {
|
|
169
|
+
const mergeFn =
|
|
169
170
|
typeof options.onChange.merge === "function"
|
|
170
171
|
? options.onChange.merge
|
|
171
|
-
: (
|
|
172
|
+
: (c: DocumentsChange<T>) => c;
|
|
173
|
+
|
|
172
174
|
handleChange = async (e: CustomEvent<DocumentsChange<T>>) => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
+
log(
|
|
176
|
+
options,
|
|
177
|
+
"Merge change",
|
|
178
|
+
e.detail,
|
|
179
|
+
"iterator",
|
|
180
|
+
newIteratorRef.id
|
|
181
|
+
);
|
|
182
|
+
const filtered = await mergeFn(e.detail);
|
|
175
183
|
if (
|
|
176
|
-
!
|
|
177
|
-
(
|
|
178
|
-
|
|
179
|
-
)
|
|
184
|
+
!filtered ||
|
|
185
|
+
(filtered.added.length === 0 &&
|
|
186
|
+
filtered.removed.length === 0)
|
|
187
|
+
)
|
|
180
188
|
return;
|
|
181
|
-
|
|
182
|
-
let merged:
|
|
189
|
+
|
|
190
|
+
let merged: Item[];
|
|
183
191
|
if (options.onChange?.update) {
|
|
184
192
|
merged = [
|
|
185
|
-
...options.onChange
|
|
186
|
-
allRef.current,
|
|
187
|
-
filteredChange
|
|
188
|
-
),
|
|
193
|
+
...options.onChange?.update(allRef.current, filtered),
|
|
189
194
|
];
|
|
190
195
|
} else {
|
|
191
196
|
merged = await db.index.updateResults(
|
|
192
|
-
allRef.current,
|
|
193
|
-
|
|
194
|
-
options
|
|
195
|
-
options
|
|
197
|
+
allRef.current as WithContext<RT>[],
|
|
198
|
+
filtered,
|
|
199
|
+
options.query || {},
|
|
200
|
+
options.resolve ?? true
|
|
196
201
|
);
|
|
197
|
-
|
|
202
|
+
|
|
203
|
+
log(options, "After update", allRef.current, merged);
|
|
198
204
|
const expectedDiff =
|
|
199
|
-
|
|
200
|
-
filteredChange.removed.length;
|
|
205
|
+
filtered.added.length - filtered.removed.length;
|
|
201
206
|
|
|
202
207
|
if (
|
|
203
208
|
merged === allRef.current ||
|
|
@@ -205,23 +210,14 @@ export const useQuery = <
|
|
|
205
210
|
merged.length === allRef.current.length)
|
|
206
211
|
) {
|
|
207
212
|
// no change
|
|
208
|
-
|
|
213
|
+
log(options, "no change after merge");
|
|
209
214
|
return;
|
|
210
215
|
}
|
|
211
216
|
}
|
|
212
217
|
|
|
213
|
-
|
|
214
|
-
added: e.detail.added.length,
|
|
215
|
-
removed: e.detail.removed.length,
|
|
216
|
-
merged: merged.length,
|
|
217
|
-
allRef: allRef.current.length,
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
updateAll(
|
|
221
|
-
options?.reverse ? merged.reverse() : merged,
|
|
222
|
-
e.detail
|
|
223
|
-
);
|
|
218
|
+
updateAll(options?.reverse ? merged.reverse() : merged);
|
|
224
219
|
};
|
|
220
|
+
|
|
225
221
|
db.events.addEventListener("change", handleChange);
|
|
226
222
|
}
|
|
227
223
|
|
|
@@ -232,137 +228,162 @@ export const useQuery = <
|
|
|
232
228
|
};
|
|
233
229
|
}, [
|
|
234
230
|
db?.closed ? undefined : db?.address,
|
|
235
|
-
options?.id
|
|
231
|
+
options?.id ?? options?.query,
|
|
236
232
|
options?.resolve,
|
|
233
|
+
options?.reverse,
|
|
234
|
+
resetCounter,
|
|
237
235
|
]);
|
|
238
236
|
|
|
239
|
-
|
|
237
|
+
/* ────────────── loadMore (once-wait aware) ────────────── */
|
|
240
238
|
const batchSize = options?.batchSize ?? 10;
|
|
239
|
+
|
|
240
|
+
const shouldWait = (): boolean => {
|
|
241
|
+
if (waitedOnceRef.current) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
if (options?.remote === false) return false;
|
|
245
|
+
if (options?.remote === true) return true;
|
|
246
|
+
if (options?.remote == null) return true;
|
|
247
|
+
if (typeof options?.remote === "object") {
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
return true;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const reloadAfterTime = (): number | undefined => {
|
|
254
|
+
if (typeof options?.remote === "object" && options.remote.eager) {
|
|
255
|
+
return options.remote.warmup ?? db?.log.timeUntilRoleMaturity;
|
|
256
|
+
}
|
|
257
|
+
return undefined;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const markWaited = () => {
|
|
261
|
+
waitedOnceRef.current = true;
|
|
262
|
+
};
|
|
263
|
+
|
|
241
264
|
const loadMore = async () => {
|
|
265
|
+
const iterator = iteratorRef.current;
|
|
242
266
|
if (
|
|
243
|
-
!
|
|
267
|
+
!iterator ||
|
|
244
268
|
emptyResultsRef.current ||
|
|
245
|
-
|
|
269
|
+
iterator.iterator.done() ||
|
|
246
270
|
loadingMoreRef.current
|
|
247
271
|
) {
|
|
248
|
-
|
|
249
|
-
isLoading,
|
|
250
|
-
emptyResultsRef: emptyResultsRef.current,
|
|
251
|
-
iteratorRef: !iteratorRef.current,
|
|
252
|
-
});
|
|
253
|
-
return;
|
|
272
|
+
return false;
|
|
254
273
|
}
|
|
255
|
-
const iterator = iteratorRef.current;
|
|
256
274
|
|
|
257
275
|
setIsLoading(true);
|
|
258
276
|
loadingMoreRef.current = true;
|
|
277
|
+
|
|
259
278
|
try {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
options,
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
279
|
+
/* ── optional replicate-wait ── */
|
|
280
|
+
if (shouldWait()) {
|
|
281
|
+
log(options, "Wait for replicators", iterator.id);
|
|
282
|
+
|
|
283
|
+
let isEager =
|
|
284
|
+
typeof options?.remote === "object" && options.remote.eager;
|
|
285
|
+
const waitTimeout =
|
|
286
|
+
typeof options?.remote === "object" &&
|
|
287
|
+
typeof options?.remote.warmup === "number"
|
|
288
|
+
? options?.remote.warmup
|
|
289
|
+
: 5_000;
|
|
290
|
+
|
|
291
|
+
let shouldResetAfterMaturity =
|
|
292
|
+
isEager && !waitedOnceRef.current;
|
|
293
|
+
let promise = db?.log
|
|
271
294
|
.waitForReplicators({
|
|
272
|
-
timeout,
|
|
295
|
+
timeout: waitTimeout,
|
|
273
296
|
signal: closeControllerRef.current?.signal,
|
|
274
297
|
})
|
|
275
298
|
.catch((e) => {
|
|
276
|
-
if (
|
|
277
|
-
|
|
299
|
+
if (
|
|
300
|
+
e instanceof AbortError ||
|
|
301
|
+
e instanceof NoPeersError
|
|
302
|
+
)
|
|
278
303
|
return;
|
|
279
|
-
}
|
|
280
304
|
console.warn("Remote replicators not ready", e);
|
|
305
|
+
})
|
|
306
|
+
.finally(() => {
|
|
307
|
+
markWaited();
|
|
308
|
+
if (shouldResetAfterMaturity) {
|
|
309
|
+
resetResultsOnReset.current = false; // don't reset results, because we expect to get same or more results
|
|
310
|
+
invokeReset();
|
|
311
|
+
}
|
|
281
312
|
});
|
|
313
|
+
if (!shouldResetAfterMaturity) {
|
|
314
|
+
await promise;
|
|
315
|
+
}
|
|
316
|
+
} else {
|
|
317
|
+
log(options, "Skip wait for replicators", iterator.id);
|
|
282
318
|
}
|
|
283
319
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
iteratorRef.current?.id,
|
|
288
|
-
"should resolve?: " + options?.resolve,
|
|
289
|
-
"query local?: " + options?.local,
|
|
290
|
-
"query remote?: " + options?.remote,
|
|
291
|
-
"isReplicating: " + (await db?.log.isReplicating())
|
|
292
|
-
);
|
|
293
|
-
|
|
294
|
-
let newItems: WithContext<RT>[] = await iterator.iterator.next(
|
|
295
|
-
batchSize
|
|
296
|
-
);
|
|
297
|
-
|
|
320
|
+
/* ── fetch next batch ── */
|
|
321
|
+
log(options, "Retrieve next batch", iterator.id);
|
|
322
|
+
let newItems = await iterator.iterator.next(batchSize);
|
|
298
323
|
if (options?.transform) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
);
|
|
324
|
+
log(options, "Transform start", iterator.id);
|
|
325
|
+
|
|
326
|
+
newItems = await Promise.all(newItems.map(options.transform));
|
|
327
|
+
log(options, "Transform end", iterator.id);
|
|
302
328
|
}
|
|
303
329
|
|
|
330
|
+
/* iterator might have been reset while we were async… */
|
|
331
|
+
|
|
304
332
|
if (iteratorRef.current !== iterator) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
logWithId(options, "Iterator ref changed, not updating state", {
|
|
308
|
-
refBefore: iterator.id,
|
|
309
|
-
currentRef: iteratorRef.current?.id,
|
|
310
|
-
ignoredItems: newItems.length,
|
|
311
|
-
});
|
|
312
|
-
return;
|
|
333
|
+
log(options, "Iterator reset while loading more");
|
|
334
|
+
return false;
|
|
313
335
|
}
|
|
314
336
|
|
|
315
|
-
|
|
316
|
-
options,
|
|
317
|
-
"loadMore: loaded more items for iterator " +
|
|
318
|
-
iteratorRef.current?.id,
|
|
319
|
-
"new items length",
|
|
320
|
-
newItems.length,
|
|
321
|
-
"all items length",
|
|
322
|
-
allRef.current.length
|
|
323
|
-
);
|
|
337
|
+
iterator.itemsConsumed += newItems.length;
|
|
324
338
|
|
|
325
339
|
emptyResultsRef.current = newItems.length === 0;
|
|
326
340
|
|
|
327
|
-
if (newItems.length
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
341
|
+
if (newItems.length) {
|
|
342
|
+
log(
|
|
343
|
+
"Loaded more items for iterator",
|
|
344
|
+
iterator.id,
|
|
345
|
+
"current id",
|
|
346
|
+
iteratorRef.current?.id,
|
|
347
|
+
"new items",
|
|
348
|
+
newItems.length,
|
|
349
|
+
"previous results",
|
|
350
|
+
allRef.current.length,
|
|
351
|
+
"batchSize",
|
|
352
|
+
batchSize,
|
|
353
|
+
"items consumed",
|
|
354
|
+
iterator.itemsConsumed
|
|
332
355
|
);
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
"no new items after dedup, not updating state. Prev length",
|
|
337
|
-
prev.length
|
|
338
|
-
);
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
const combined = options?.reverse
|
|
342
|
-
? [...newItemsNoHash.reverse(), ...prev]
|
|
343
|
-
: [...prev, ...newItemsNoHash];
|
|
344
|
-
updateAll(combined, null);
|
|
345
|
-
} else {
|
|
346
|
-
logWithId(
|
|
347
|
-
options,
|
|
348
|
-
"no new items, not updating state for iterator " +
|
|
349
|
-
iteratorRef.current?.id +
|
|
350
|
-
" existing results length",
|
|
351
|
-
allRef.current.length
|
|
356
|
+
const prev = allRef.current;
|
|
357
|
+
const dedup = new Set(
|
|
358
|
+
prev.map((x) => (x as any).__context.head)
|
|
352
359
|
);
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
360
|
+
const unique = newItems.filter(
|
|
361
|
+
(x) => !dedup.has((x as any).__context.head)
|
|
362
|
+
);
|
|
363
|
+
if (!unique.length) return;
|
|
364
|
+
|
|
365
|
+
const combined = reverseRef.current
|
|
366
|
+
? [...unique.reverse(), ...prev]
|
|
367
|
+
: [...prev, ...unique];
|
|
368
|
+
updateAll(combined);
|
|
358
369
|
} else {
|
|
359
|
-
|
|
370
|
+
log(options, "No new items", iterator.id);
|
|
360
371
|
}
|
|
372
|
+
return !iterator.iterator.done();
|
|
373
|
+
} catch (e) {
|
|
374
|
+
if (!(e instanceof ClosedError)) throw e;
|
|
361
375
|
} finally {
|
|
362
376
|
setIsLoading(false);
|
|
363
377
|
loadingMoreRef.current = false;
|
|
364
378
|
}
|
|
365
379
|
};
|
|
366
380
|
|
|
367
|
-
|
|
381
|
+
/* ────────────── public API ────────────── */
|
|
382
|
+
return {
|
|
383
|
+
items: all,
|
|
384
|
+
loadMore,
|
|
385
|
+
isLoading,
|
|
386
|
+
empty: () => emptyResultsRef.current,
|
|
387
|
+
id: id,
|
|
388
|
+
};
|
|
368
389
|
};
|