@spoosh/react 0.10.1 → 0.12.0
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/README.md +58 -41
- package/dist/index.d.mts +178 -90
- package/dist/index.d.ts +178 -90
- package/dist/index.js +132 -51
- package/dist/index.mjs +137 -52
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -404,12 +404,12 @@ function createUseWrite(options) {
|
|
|
404
404
|
return useWrite;
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
-
// src/
|
|
407
|
+
// src/usePages/index.ts
|
|
408
408
|
var import_react3 = require("react");
|
|
409
409
|
var import_core3 = require("@spoosh/core");
|
|
410
|
-
function
|
|
410
|
+
function createUsePages(options) {
|
|
411
411
|
const { api, stateManager, eventEmitter, pluginExecutor } = options;
|
|
412
|
-
return function
|
|
412
|
+
return function usePages(readFn, readOptions) {
|
|
413
413
|
const {
|
|
414
414
|
enabled = true,
|
|
415
415
|
tags,
|
|
@@ -432,7 +432,7 @@ function createUseInfiniteRead(options) {
|
|
|
432
432
|
const capturedCall = selectorResultRef.current.call;
|
|
433
433
|
if (!capturedCall) {
|
|
434
434
|
throw new Error(
|
|
435
|
-
'
|
|
435
|
+
'usePages requires calling an HTTP method (GET). Example: usePages((api) => api("posts").GET())'
|
|
436
436
|
);
|
|
437
437
|
}
|
|
438
438
|
const requestOptions = capturedCall.options;
|
|
@@ -442,12 +442,6 @@ function createUseInfiniteRead(options) {
|
|
|
442
442
|
params: requestOptions?.params,
|
|
443
443
|
body: requestOptions?.body
|
|
444
444
|
};
|
|
445
|
-
const baseOptionsForKey = {
|
|
446
|
-
...capturedCall.options,
|
|
447
|
-
query: void 0,
|
|
448
|
-
params: void 0,
|
|
449
|
-
body: void 0
|
|
450
|
-
};
|
|
451
445
|
const resolvedPath = (0, import_core3.resolvePath)(pathSegments, requestOptions?.params);
|
|
452
446
|
const resolvedTags = (0, import_core3.resolveTags)({ tags }, resolvedPath);
|
|
453
447
|
const canFetchNextRef = (0, import_react3.useRef)(canFetchNext);
|
|
@@ -463,22 +457,31 @@ function createUseInfiniteRead(options) {
|
|
|
463
457
|
const queryKey = stateManager.createQueryKey({
|
|
464
458
|
path: capturedCall.path,
|
|
465
459
|
method: capturedCall.method,
|
|
466
|
-
options:
|
|
460
|
+
options: capturedCall.options
|
|
461
|
+
});
|
|
462
|
+
const lifecycleRef = (0, import_react3.useRef)({
|
|
463
|
+
initialized: false,
|
|
464
|
+
prevContext: null,
|
|
465
|
+
lastQueryKey: null
|
|
467
466
|
});
|
|
468
467
|
const controllerRef = (0, import_react3.useRef)(null);
|
|
469
|
-
|
|
468
|
+
const queryKeyChanged = controllerRef.current !== null && controllerRef.current.queryKey !== queryKey;
|
|
469
|
+
if (queryKeyChanged) {
|
|
470
|
+
lifecycleRef.current.prevContext = controllerRef.current.controller.getContext();
|
|
471
|
+
lifecycleRef.current.initialized = false;
|
|
472
|
+
}
|
|
473
|
+
if (!controllerRef.current || queryKeyChanged) {
|
|
470
474
|
controllerRef.current = {
|
|
471
475
|
controller: (0, import_core3.createInfiniteReadController)({
|
|
472
476
|
path: capturedCall.path,
|
|
473
477
|
method: capturedCall.method,
|
|
474
478
|
tags: resolvedTags,
|
|
475
479
|
initialRequest,
|
|
476
|
-
|
|
477
|
-
canFetchNext: (ctx) => canFetchNextRef.current(ctx),
|
|
480
|
+
canFetchNext: canFetchNext ? (ctx) => canFetchNextRef.current?.(ctx) ?? false : void 0,
|
|
478
481
|
canFetchPrev: canFetchPrev ? (ctx) => canFetchPrevRef.current?.(ctx) ?? false : void 0,
|
|
479
|
-
nextPageRequest: (ctx) => nextPageRequestRef.current(ctx),
|
|
482
|
+
nextPageRequest: nextPageRequest ? (ctx) => nextPageRequestRef.current?.(ctx) ?? {} : void 0,
|
|
480
483
|
prevPageRequest: prevPageRequest ? (ctx) => prevPageRequestRef.current?.(ctx) ?? {} : void 0,
|
|
481
|
-
merger: (
|
|
484
|
+
merger: (pages) => mergerRef.current(pages),
|
|
482
485
|
stateManager,
|
|
483
486
|
eventEmitter,
|
|
484
487
|
pluginExecutor,
|
|
@@ -488,9 +491,7 @@ function createUseInfiniteRead(options) {
|
|
|
488
491
|
const method = pathMethods[capturedCall.method];
|
|
489
492
|
const fetchOptions = {
|
|
490
493
|
...capturedCall.options,
|
|
491
|
-
|
|
492
|
-
params: opts.params,
|
|
493
|
-
body: opts.body,
|
|
494
|
+
...opts,
|
|
494
495
|
signal
|
|
495
496
|
};
|
|
496
497
|
return method(fetchOptions);
|
|
@@ -501,11 +502,12 @@ function createUseInfiniteRead(options) {
|
|
|
501
502
|
}
|
|
502
503
|
const controller = controllerRef.current.controller;
|
|
503
504
|
controller.setPluginOptions(pluginOpts);
|
|
504
|
-
const
|
|
505
|
-
controller.subscribe,
|
|
506
|
-
controller
|
|
507
|
-
controller.getState
|
|
505
|
+
const subscribe = (0, import_react3.useCallback)(
|
|
506
|
+
(callback) => controller.subscribe(callback),
|
|
507
|
+
[controller]
|
|
508
508
|
);
|
|
509
|
+
const getSnapshot = (0, import_react3.useCallback)(() => controller.getState(), [controller]);
|
|
510
|
+
const state = (0, import_react3.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
509
511
|
const [isPending, setIsPending] = (0, import_react3.useState)(() => {
|
|
510
512
|
return enabled && state.data === void 0;
|
|
511
513
|
});
|
|
@@ -515,10 +517,6 @@ function createUseInfiniteRead(options) {
|
|
|
515
517
|
const fetchingPrev = fetchingDirection === "prev";
|
|
516
518
|
const hasData = state.data !== void 0;
|
|
517
519
|
const loading = (isPending || fetching) && !hasData;
|
|
518
|
-
const lifecycleRef = (0, import_react3.useRef)({
|
|
519
|
-
initialized: false,
|
|
520
|
-
prevContext: null
|
|
521
|
-
});
|
|
522
520
|
const tagsKey = JSON.stringify(tags);
|
|
523
521
|
(0, import_react3.useEffect)(() => {
|
|
524
522
|
return () => {
|
|
@@ -527,8 +525,27 @@ function createUseInfiniteRead(options) {
|
|
|
527
525
|
};
|
|
528
526
|
}, []);
|
|
529
527
|
(0, import_react3.useEffect)(() => {
|
|
530
|
-
|
|
531
|
-
|
|
528
|
+
if (!enabled) return;
|
|
529
|
+
const { initialized, prevContext, lastQueryKey } = lifecycleRef.current;
|
|
530
|
+
const isQueryKeyChange = lastQueryKey !== null && lastQueryKey !== queryKey;
|
|
531
|
+
if (!initialized) {
|
|
532
|
+
controller.mount();
|
|
533
|
+
lifecycleRef.current.initialized = true;
|
|
534
|
+
if (prevContext) {
|
|
535
|
+
controller.update(prevContext);
|
|
536
|
+
lifecycleRef.current.prevContext = null;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
lifecycleRef.current.lastQueryKey = queryKey;
|
|
540
|
+
const currentState = controller.getState();
|
|
541
|
+
const isFetching = controller.getFetchingDirection() !== null;
|
|
542
|
+
if (isQueryKeyChange) {
|
|
543
|
+
setIsPending(true);
|
|
544
|
+
controller.trigger({ force: false }).finally(() => setIsPending(false));
|
|
545
|
+
} else if (currentState.data === void 0 && !isFetching) {
|
|
546
|
+
setIsPending(true);
|
|
547
|
+
controller.fetchNext().finally(() => setIsPending(false));
|
|
548
|
+
}
|
|
532
549
|
const unsubInvalidate = eventEmitter.on(
|
|
533
550
|
"invalidate",
|
|
534
551
|
(invalidatedTags) => {
|
|
@@ -537,41 +554,32 @@ function createUseInfiniteRead(options) {
|
|
|
537
554
|
);
|
|
538
555
|
if (hasMatch) {
|
|
539
556
|
setIsPending(true);
|
|
540
|
-
controller.
|
|
557
|
+
controller.trigger().finally(() => setIsPending(false));
|
|
541
558
|
}
|
|
542
559
|
}
|
|
543
560
|
);
|
|
544
561
|
const unsubRefetchAll = eventEmitter.on("refetchAll", () => {
|
|
545
562
|
setIsPending(true);
|
|
546
|
-
controller.
|
|
563
|
+
controller.trigger().finally(() => setIsPending(false));
|
|
547
564
|
});
|
|
548
565
|
return () => {
|
|
566
|
+
controller.unmount();
|
|
549
567
|
unsubInvalidate();
|
|
550
568
|
unsubRefetchAll();
|
|
551
569
|
};
|
|
552
|
-
}, [tagsKey]);
|
|
553
|
-
|
|
554
|
-
if (!lifecycleRef.current.initialized) return;
|
|
555
|
-
if (enabled) {
|
|
556
|
-
const currentState = controller.getState();
|
|
557
|
-
const isFetching = controller.getFetchingDirection() !== null;
|
|
558
|
-
if (currentState.data === void 0 && !isFetching) {
|
|
559
|
-
setIsPending(true);
|
|
560
|
-
controller.fetchNext().finally(() => setIsPending(false));
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}, [enabled]);
|
|
570
|
+
}, [queryKey, enabled, tagsKey]);
|
|
571
|
+
const pluginOptsKey = JSON.stringify(pluginOpts);
|
|
564
572
|
(0, import_react3.useEffect)(() => {
|
|
565
573
|
if (!enabled || !lifecycleRef.current.initialized) return;
|
|
566
574
|
const prevContext = controller.getContext();
|
|
567
575
|
controller.update(prevContext);
|
|
568
|
-
}, [
|
|
569
|
-
const
|
|
570
|
-
|
|
576
|
+
}, [pluginOptsKey]);
|
|
577
|
+
const trigger = async (options2) => {
|
|
578
|
+
await controller.trigger(options2);
|
|
579
|
+
};
|
|
571
580
|
const result = {
|
|
572
|
-
meta: pluginResultData,
|
|
573
581
|
data: state.data,
|
|
574
|
-
|
|
582
|
+
pages: state.pages,
|
|
575
583
|
loading,
|
|
576
584
|
fetching,
|
|
577
585
|
fetchingNext,
|
|
@@ -580,7 +588,7 @@ function createUseInfiniteRead(options) {
|
|
|
580
588
|
canFetchPrev: state.canFetchPrev,
|
|
581
589
|
fetchNext: controller.fetchNext,
|
|
582
590
|
fetchPrev: controller.fetchPrev,
|
|
583
|
-
trigger
|
|
591
|
+
trigger,
|
|
584
592
|
abort: controller.abort,
|
|
585
593
|
error: state.error
|
|
586
594
|
};
|
|
@@ -588,6 +596,72 @@ function createUseInfiniteRead(options) {
|
|
|
588
596
|
};
|
|
589
597
|
}
|
|
590
598
|
|
|
599
|
+
// src/useQueue/index.ts
|
|
600
|
+
var import_react4 = require("react");
|
|
601
|
+
var import_core4 = require("@spoosh/core");
|
|
602
|
+
function createUseQueue(options) {
|
|
603
|
+
const { api, stateManager, pluginExecutor, eventEmitter } = options;
|
|
604
|
+
function useQueue(queueFn, queueOptions) {
|
|
605
|
+
(0, import_react4.useId)();
|
|
606
|
+
const selectorResultRef = (0, import_react4.useRef)({
|
|
607
|
+
call: null,
|
|
608
|
+
selector: null
|
|
609
|
+
});
|
|
610
|
+
const selectorProxy = (0, import_core4.createSelectorProxy)((result) => {
|
|
611
|
+
selectorResultRef.current = result;
|
|
612
|
+
});
|
|
613
|
+
queueFn(selectorProxy);
|
|
614
|
+
const capturedCall = selectorResultRef.current.call;
|
|
615
|
+
const capturedSelector = selectorResultRef.current.selector;
|
|
616
|
+
const captured = capturedCall ?? capturedSelector;
|
|
617
|
+
if (!captured) {
|
|
618
|
+
throw new Error(
|
|
619
|
+
'useQueue requires selecting an HTTP method. Example: useQueue((api) => api("uploads").POST())'
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
const { concurrency, ...hookOptions } = queueOptions ?? {};
|
|
623
|
+
const controllerRef = (0, import_react4.useRef)(null);
|
|
624
|
+
if (!controllerRef.current) {
|
|
625
|
+
const config = {
|
|
626
|
+
path: captured.path,
|
|
627
|
+
method: captured.method,
|
|
628
|
+
concurrency,
|
|
629
|
+
operationType: "queue",
|
|
630
|
+
hookOptions
|
|
631
|
+
};
|
|
632
|
+
controllerRef.current = (0, import_core4.createQueueController)(config, {
|
|
633
|
+
api,
|
|
634
|
+
stateManager,
|
|
635
|
+
eventEmitter,
|
|
636
|
+
pluginExecutor
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
const controller = controllerRef.current;
|
|
640
|
+
(0, import_react4.useEffect)(() => {
|
|
641
|
+
if (concurrency !== void 0) {
|
|
642
|
+
controller.setConcurrency(concurrency);
|
|
643
|
+
}
|
|
644
|
+
}, [concurrency, controller]);
|
|
645
|
+
const tasks = (0, import_react4.useSyncExternalStore)(
|
|
646
|
+
controller.subscribe,
|
|
647
|
+
controller.getQueue,
|
|
648
|
+
controller.getQueue
|
|
649
|
+
);
|
|
650
|
+
return {
|
|
651
|
+
trigger: (input) => controller.trigger(input ?? {}),
|
|
652
|
+
tasks,
|
|
653
|
+
stats: controller.getStats(),
|
|
654
|
+
abort: controller.abort,
|
|
655
|
+
retry: controller.retry,
|
|
656
|
+
remove: controller.remove,
|
|
657
|
+
removeSettled: controller.removeSettled,
|
|
658
|
+
clear: controller.clear
|
|
659
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
return useQueue;
|
|
663
|
+
}
|
|
664
|
+
|
|
591
665
|
// src/create/index.ts
|
|
592
666
|
function create(instance) {
|
|
593
667
|
const { api, stateManager, eventEmitter, pluginExecutor } = instance;
|
|
@@ -603,7 +677,13 @@ function create(instance) {
|
|
|
603
677
|
eventEmitter,
|
|
604
678
|
pluginExecutor
|
|
605
679
|
});
|
|
606
|
-
const
|
|
680
|
+
const usePages = createUsePages({
|
|
681
|
+
api,
|
|
682
|
+
stateManager,
|
|
683
|
+
eventEmitter,
|
|
684
|
+
pluginExecutor
|
|
685
|
+
});
|
|
686
|
+
const useQueue = createUseQueue({
|
|
607
687
|
api,
|
|
608
688
|
stateManager,
|
|
609
689
|
eventEmitter,
|
|
@@ -636,7 +716,8 @@ function create(instance) {
|
|
|
636
716
|
return {
|
|
637
717
|
useRead,
|
|
638
718
|
useWrite,
|
|
639
|
-
|
|
719
|
+
usePages,
|
|
720
|
+
useQueue,
|
|
640
721
|
...instanceApis
|
|
641
722
|
};
|
|
642
723
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -402,13 +402,14 @@ function createUseWrite(options) {
|
|
|
402
402
|
return useWrite;
|
|
403
403
|
}
|
|
404
404
|
|
|
405
|
-
// src/
|
|
405
|
+
// src/usePages/index.ts
|
|
406
406
|
import {
|
|
407
407
|
useRef as useRef3,
|
|
408
408
|
useEffect as useEffect2,
|
|
409
409
|
useSyncExternalStore as useSyncExternalStore3,
|
|
410
410
|
useId as useId3,
|
|
411
|
-
useState as useState3
|
|
411
|
+
useState as useState3,
|
|
412
|
+
useCallback as useCallback3
|
|
412
413
|
} from "react";
|
|
413
414
|
import {
|
|
414
415
|
createInfiniteReadController,
|
|
@@ -416,9 +417,9 @@ import {
|
|
|
416
417
|
resolvePath as resolvePath3,
|
|
417
418
|
resolveTags as resolveTags3
|
|
418
419
|
} from "@spoosh/core";
|
|
419
|
-
function
|
|
420
|
+
function createUsePages(options) {
|
|
420
421
|
const { api, stateManager, eventEmitter, pluginExecutor } = options;
|
|
421
|
-
return function
|
|
422
|
+
return function usePages(readFn, readOptions) {
|
|
422
423
|
const {
|
|
423
424
|
enabled = true,
|
|
424
425
|
tags,
|
|
@@ -441,7 +442,7 @@ function createUseInfiniteRead(options) {
|
|
|
441
442
|
const capturedCall = selectorResultRef.current.call;
|
|
442
443
|
if (!capturedCall) {
|
|
443
444
|
throw new Error(
|
|
444
|
-
'
|
|
445
|
+
'usePages requires calling an HTTP method (GET). Example: usePages((api) => api("posts").GET())'
|
|
445
446
|
);
|
|
446
447
|
}
|
|
447
448
|
const requestOptions = capturedCall.options;
|
|
@@ -451,12 +452,6 @@ function createUseInfiniteRead(options) {
|
|
|
451
452
|
params: requestOptions?.params,
|
|
452
453
|
body: requestOptions?.body
|
|
453
454
|
};
|
|
454
|
-
const baseOptionsForKey = {
|
|
455
|
-
...capturedCall.options,
|
|
456
|
-
query: void 0,
|
|
457
|
-
params: void 0,
|
|
458
|
-
body: void 0
|
|
459
|
-
};
|
|
460
455
|
const resolvedPath = resolvePath3(pathSegments, requestOptions?.params);
|
|
461
456
|
const resolvedTags = resolveTags3({ tags }, resolvedPath);
|
|
462
457
|
const canFetchNextRef = useRef3(canFetchNext);
|
|
@@ -472,22 +467,31 @@ function createUseInfiniteRead(options) {
|
|
|
472
467
|
const queryKey = stateManager.createQueryKey({
|
|
473
468
|
path: capturedCall.path,
|
|
474
469
|
method: capturedCall.method,
|
|
475
|
-
options:
|
|
470
|
+
options: capturedCall.options
|
|
471
|
+
});
|
|
472
|
+
const lifecycleRef = useRef3({
|
|
473
|
+
initialized: false,
|
|
474
|
+
prevContext: null,
|
|
475
|
+
lastQueryKey: null
|
|
476
476
|
});
|
|
477
477
|
const controllerRef = useRef3(null);
|
|
478
|
-
|
|
478
|
+
const queryKeyChanged = controllerRef.current !== null && controllerRef.current.queryKey !== queryKey;
|
|
479
|
+
if (queryKeyChanged) {
|
|
480
|
+
lifecycleRef.current.prevContext = controllerRef.current.controller.getContext();
|
|
481
|
+
lifecycleRef.current.initialized = false;
|
|
482
|
+
}
|
|
483
|
+
if (!controllerRef.current || queryKeyChanged) {
|
|
479
484
|
controllerRef.current = {
|
|
480
485
|
controller: createInfiniteReadController({
|
|
481
486
|
path: capturedCall.path,
|
|
482
487
|
method: capturedCall.method,
|
|
483
488
|
tags: resolvedTags,
|
|
484
489
|
initialRequest,
|
|
485
|
-
|
|
486
|
-
canFetchNext: (ctx) => canFetchNextRef.current(ctx),
|
|
490
|
+
canFetchNext: canFetchNext ? (ctx) => canFetchNextRef.current?.(ctx) ?? false : void 0,
|
|
487
491
|
canFetchPrev: canFetchPrev ? (ctx) => canFetchPrevRef.current?.(ctx) ?? false : void 0,
|
|
488
|
-
nextPageRequest: (ctx) => nextPageRequestRef.current(ctx),
|
|
492
|
+
nextPageRequest: nextPageRequest ? (ctx) => nextPageRequestRef.current?.(ctx) ?? {} : void 0,
|
|
489
493
|
prevPageRequest: prevPageRequest ? (ctx) => prevPageRequestRef.current?.(ctx) ?? {} : void 0,
|
|
490
|
-
merger: (
|
|
494
|
+
merger: (pages) => mergerRef.current(pages),
|
|
491
495
|
stateManager,
|
|
492
496
|
eventEmitter,
|
|
493
497
|
pluginExecutor,
|
|
@@ -497,9 +501,7 @@ function createUseInfiniteRead(options) {
|
|
|
497
501
|
const method = pathMethods[capturedCall.method];
|
|
498
502
|
const fetchOptions = {
|
|
499
503
|
...capturedCall.options,
|
|
500
|
-
|
|
501
|
-
params: opts.params,
|
|
502
|
-
body: opts.body,
|
|
504
|
+
...opts,
|
|
503
505
|
signal
|
|
504
506
|
};
|
|
505
507
|
return method(fetchOptions);
|
|
@@ -510,11 +512,12 @@ function createUseInfiniteRead(options) {
|
|
|
510
512
|
}
|
|
511
513
|
const controller = controllerRef.current.controller;
|
|
512
514
|
controller.setPluginOptions(pluginOpts);
|
|
513
|
-
const
|
|
514
|
-
controller.subscribe,
|
|
515
|
-
controller
|
|
516
|
-
controller.getState
|
|
515
|
+
const subscribe = useCallback3(
|
|
516
|
+
(callback) => controller.subscribe(callback),
|
|
517
|
+
[controller]
|
|
517
518
|
);
|
|
519
|
+
const getSnapshot = useCallback3(() => controller.getState(), [controller]);
|
|
520
|
+
const state = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
|
|
518
521
|
const [isPending, setIsPending] = useState3(() => {
|
|
519
522
|
return enabled && state.data === void 0;
|
|
520
523
|
});
|
|
@@ -524,10 +527,6 @@ function createUseInfiniteRead(options) {
|
|
|
524
527
|
const fetchingPrev = fetchingDirection === "prev";
|
|
525
528
|
const hasData = state.data !== void 0;
|
|
526
529
|
const loading = (isPending || fetching) && !hasData;
|
|
527
|
-
const lifecycleRef = useRef3({
|
|
528
|
-
initialized: false,
|
|
529
|
-
prevContext: null
|
|
530
|
-
});
|
|
531
530
|
const tagsKey = JSON.stringify(tags);
|
|
532
531
|
useEffect2(() => {
|
|
533
532
|
return () => {
|
|
@@ -536,8 +535,27 @@ function createUseInfiniteRead(options) {
|
|
|
536
535
|
};
|
|
537
536
|
}, []);
|
|
538
537
|
useEffect2(() => {
|
|
539
|
-
|
|
540
|
-
|
|
538
|
+
if (!enabled) return;
|
|
539
|
+
const { initialized, prevContext, lastQueryKey } = lifecycleRef.current;
|
|
540
|
+
const isQueryKeyChange = lastQueryKey !== null && lastQueryKey !== queryKey;
|
|
541
|
+
if (!initialized) {
|
|
542
|
+
controller.mount();
|
|
543
|
+
lifecycleRef.current.initialized = true;
|
|
544
|
+
if (prevContext) {
|
|
545
|
+
controller.update(prevContext);
|
|
546
|
+
lifecycleRef.current.prevContext = null;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
lifecycleRef.current.lastQueryKey = queryKey;
|
|
550
|
+
const currentState = controller.getState();
|
|
551
|
+
const isFetching = controller.getFetchingDirection() !== null;
|
|
552
|
+
if (isQueryKeyChange) {
|
|
553
|
+
setIsPending(true);
|
|
554
|
+
controller.trigger({ force: false }).finally(() => setIsPending(false));
|
|
555
|
+
} else if (currentState.data === void 0 && !isFetching) {
|
|
556
|
+
setIsPending(true);
|
|
557
|
+
controller.fetchNext().finally(() => setIsPending(false));
|
|
558
|
+
}
|
|
541
559
|
const unsubInvalidate = eventEmitter.on(
|
|
542
560
|
"invalidate",
|
|
543
561
|
(invalidatedTags) => {
|
|
@@ -546,41 +564,32 @@ function createUseInfiniteRead(options) {
|
|
|
546
564
|
);
|
|
547
565
|
if (hasMatch) {
|
|
548
566
|
setIsPending(true);
|
|
549
|
-
controller.
|
|
567
|
+
controller.trigger().finally(() => setIsPending(false));
|
|
550
568
|
}
|
|
551
569
|
}
|
|
552
570
|
);
|
|
553
571
|
const unsubRefetchAll = eventEmitter.on("refetchAll", () => {
|
|
554
572
|
setIsPending(true);
|
|
555
|
-
controller.
|
|
573
|
+
controller.trigger().finally(() => setIsPending(false));
|
|
556
574
|
});
|
|
557
575
|
return () => {
|
|
576
|
+
controller.unmount();
|
|
558
577
|
unsubInvalidate();
|
|
559
578
|
unsubRefetchAll();
|
|
560
579
|
};
|
|
561
|
-
}, [tagsKey]);
|
|
562
|
-
|
|
563
|
-
if (!lifecycleRef.current.initialized) return;
|
|
564
|
-
if (enabled) {
|
|
565
|
-
const currentState = controller.getState();
|
|
566
|
-
const isFetching = controller.getFetchingDirection() !== null;
|
|
567
|
-
if (currentState.data === void 0 && !isFetching) {
|
|
568
|
-
setIsPending(true);
|
|
569
|
-
controller.fetchNext().finally(() => setIsPending(false));
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
}, [enabled]);
|
|
580
|
+
}, [queryKey, enabled, tagsKey]);
|
|
581
|
+
const pluginOptsKey = JSON.stringify(pluginOpts);
|
|
573
582
|
useEffect2(() => {
|
|
574
583
|
if (!enabled || !lifecycleRef.current.initialized) return;
|
|
575
584
|
const prevContext = controller.getContext();
|
|
576
585
|
controller.update(prevContext);
|
|
577
|
-
}, [
|
|
578
|
-
const
|
|
579
|
-
|
|
586
|
+
}, [pluginOptsKey]);
|
|
587
|
+
const trigger = async (options2) => {
|
|
588
|
+
await controller.trigger(options2);
|
|
589
|
+
};
|
|
580
590
|
const result = {
|
|
581
|
-
meta: pluginResultData,
|
|
582
591
|
data: state.data,
|
|
583
|
-
|
|
592
|
+
pages: state.pages,
|
|
584
593
|
loading,
|
|
585
594
|
fetching,
|
|
586
595
|
fetchingNext,
|
|
@@ -589,7 +598,7 @@ function createUseInfiniteRead(options) {
|
|
|
589
598
|
canFetchPrev: state.canFetchPrev,
|
|
590
599
|
fetchNext: controller.fetchNext,
|
|
591
600
|
fetchPrev: controller.fetchPrev,
|
|
592
|
-
trigger
|
|
601
|
+
trigger,
|
|
593
602
|
abort: controller.abort,
|
|
594
603
|
error: state.error
|
|
595
604
|
};
|
|
@@ -597,6 +606,75 @@ function createUseInfiniteRead(options) {
|
|
|
597
606
|
};
|
|
598
607
|
}
|
|
599
608
|
|
|
609
|
+
// src/useQueue/index.ts
|
|
610
|
+
import { useSyncExternalStore as useSyncExternalStore4, useRef as useRef4, useId as useId4, useEffect as useEffect3 } from "react";
|
|
611
|
+
import {
|
|
612
|
+
createSelectorProxy as createSelectorProxy4,
|
|
613
|
+
createQueueController
|
|
614
|
+
} from "@spoosh/core";
|
|
615
|
+
function createUseQueue(options) {
|
|
616
|
+
const { api, stateManager, pluginExecutor, eventEmitter } = options;
|
|
617
|
+
function useQueue(queueFn, queueOptions) {
|
|
618
|
+
useId4();
|
|
619
|
+
const selectorResultRef = useRef4({
|
|
620
|
+
call: null,
|
|
621
|
+
selector: null
|
|
622
|
+
});
|
|
623
|
+
const selectorProxy = createSelectorProxy4((result) => {
|
|
624
|
+
selectorResultRef.current = result;
|
|
625
|
+
});
|
|
626
|
+
queueFn(selectorProxy);
|
|
627
|
+
const capturedCall = selectorResultRef.current.call;
|
|
628
|
+
const capturedSelector = selectorResultRef.current.selector;
|
|
629
|
+
const captured = capturedCall ?? capturedSelector;
|
|
630
|
+
if (!captured) {
|
|
631
|
+
throw new Error(
|
|
632
|
+
'useQueue requires selecting an HTTP method. Example: useQueue((api) => api("uploads").POST())'
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
const { concurrency, ...hookOptions } = queueOptions ?? {};
|
|
636
|
+
const controllerRef = useRef4(null);
|
|
637
|
+
if (!controllerRef.current) {
|
|
638
|
+
const config = {
|
|
639
|
+
path: captured.path,
|
|
640
|
+
method: captured.method,
|
|
641
|
+
concurrency,
|
|
642
|
+
operationType: "queue",
|
|
643
|
+
hookOptions
|
|
644
|
+
};
|
|
645
|
+
controllerRef.current = createQueueController(config, {
|
|
646
|
+
api,
|
|
647
|
+
stateManager,
|
|
648
|
+
eventEmitter,
|
|
649
|
+
pluginExecutor
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
const controller = controllerRef.current;
|
|
653
|
+
useEffect3(() => {
|
|
654
|
+
if (concurrency !== void 0) {
|
|
655
|
+
controller.setConcurrency(concurrency);
|
|
656
|
+
}
|
|
657
|
+
}, [concurrency, controller]);
|
|
658
|
+
const tasks = useSyncExternalStore4(
|
|
659
|
+
controller.subscribe,
|
|
660
|
+
controller.getQueue,
|
|
661
|
+
controller.getQueue
|
|
662
|
+
);
|
|
663
|
+
return {
|
|
664
|
+
trigger: (input) => controller.trigger(input ?? {}),
|
|
665
|
+
tasks,
|
|
666
|
+
stats: controller.getStats(),
|
|
667
|
+
abort: controller.abort,
|
|
668
|
+
retry: controller.retry,
|
|
669
|
+
remove: controller.remove,
|
|
670
|
+
removeSettled: controller.removeSettled,
|
|
671
|
+
clear: controller.clear
|
|
672
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
return useQueue;
|
|
676
|
+
}
|
|
677
|
+
|
|
600
678
|
// src/create/index.ts
|
|
601
679
|
function create(instance) {
|
|
602
680
|
const { api, stateManager, eventEmitter, pluginExecutor } = instance;
|
|
@@ -612,7 +690,13 @@ function create(instance) {
|
|
|
612
690
|
eventEmitter,
|
|
613
691
|
pluginExecutor
|
|
614
692
|
});
|
|
615
|
-
const
|
|
693
|
+
const usePages = createUsePages({
|
|
694
|
+
api,
|
|
695
|
+
stateManager,
|
|
696
|
+
eventEmitter,
|
|
697
|
+
pluginExecutor
|
|
698
|
+
});
|
|
699
|
+
const useQueue = createUseQueue({
|
|
616
700
|
api,
|
|
617
701
|
stateManager,
|
|
618
702
|
eventEmitter,
|
|
@@ -645,7 +729,8 @@ function create(instance) {
|
|
|
645
729
|
return {
|
|
646
730
|
useRead,
|
|
647
731
|
useWrite,
|
|
648
|
-
|
|
732
|
+
usePages,
|
|
733
|
+
useQueue,
|
|
649
734
|
...instanceApis
|
|
650
735
|
};
|
|
651
736
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spoosh/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React hooks for Spoosh API toolkit",
|
|
6
6
|
"keywords": [
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@spoosh/core": ">=0.
|
|
37
|
+
"@spoosh/core": ">=0.15.0",
|
|
38
38
|
"react": "^18 || ^19"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@testing-library/react": "^16.0.0",
|
|
42
42
|
"jsdom": "^26.0.0",
|
|
43
|
-
"@spoosh/core": "0.
|
|
44
|
-
"@spoosh/test-utils": "0.
|
|
43
|
+
"@spoosh/core": "0.15.0",
|
|
44
|
+
"@spoosh/test-utils": "0.3.0"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"dev": "tsup --watch",
|