@adventurelabs/scout-core 1.4.32 → 1.4.33

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.
@@ -426,10 +426,13 @@ export const useInfiniteFeedByHerd = (herdId, options) => {
426
426
  const prevHerdIdRef = useRef(undefined);
427
427
  const lastAddedCursorRef = useRef(undefined);
428
428
  const pagesLengthRef = useRef(0);
429
+ /** When true, pass null to the query so we don't request (newHerdId, oldCursor) before state commits. */
430
+ const forceNullCursorRef = useRef(false);
431
+ const cursorForQuery = forceNullCursorRef.current ? null : currentCursor;
429
432
  const currentQuery = useGetFeedInfiniteByHerdQuery({
430
433
  herdId,
431
434
  limit,
432
- cursor: currentCursor,
435
+ cursor: cursorForQuery,
433
436
  supabase: options.supabase,
434
437
  }, { skip: !enabled });
435
438
  const isLoading = currentQuery.isLoading;
@@ -442,6 +445,7 @@ export const useInfiniteFeedByHerd = (herdId, options) => {
442
445
  prevHerdIdRef.current !== herdId &&
443
446
  enabled &&
444
447
  herdId) {
448
+ forceNullCursorRef.current = true;
445
449
  setPages([]);
446
450
  setCurrentCursor(null);
447
451
  setCurrentResult(null);
@@ -457,19 +461,26 @@ export const useInfiniteFeedByHerd = (herdId, options) => {
457
461
  useEffect(() => {
458
462
  if (!currentQuery.data || currentQuery.isLoading)
459
463
  return;
460
- const cursor = currentCursor;
464
+ const cursor = cursorForQuery;
461
465
  const items = Array.isArray(currentQuery.data.items)
462
466
  ? currentQuery.data.items
463
467
  : [];
464
- const hasMore = currentQuery.data.hasMore ?? false;
465
468
  const nextCursor = currentQuery.data.nextCursor ?? null;
469
+ // Derive hasMore from items we received (like dummy), so we don't get stuck when API
470
+ // returns hasMore: false e.g. due to RPC/PostgREST returning fewer rows than limit.
471
+ const hasMore = (items.length >= limit || (items.length > 0 && items.length < limit)) &&
472
+ nextCursor != null;
473
+ // After herd switch we force null cursor; once we've merged that first page, allow normal cursor again
474
+ if (cursor === null) {
475
+ forceNullCursorRef.current = false;
476
+ }
466
477
  // Only update currentResult for successful response (match dummy)
467
478
  if (items.length === 0 && cursor === null) {
468
479
  // Leave currentResult unchanged on spurious empty first page
469
480
  }
470
481
  else {
471
482
  setCurrentResult({
472
- hasMore: hasMore && nextCursor != null,
483
+ hasMore,
473
484
  nextCursor,
474
485
  });
475
486
  }
@@ -501,7 +512,7 @@ export const useInfiniteFeedByHerd = (herdId, options) => {
501
512
  }, [
502
513
  currentQuery.data,
503
514
  currentQuery.isLoading,
504
- currentCursor,
515
+ cursorForQuery,
505
516
  pages.length,
506
517
  limit,
507
518
  ]);
@@ -513,6 +524,7 @@ export const useInfiniteFeedByHerd = (herdId, options) => {
513
524
  }
514
525
  }, [currentResult, isLoading]);
515
526
  const refetch = useCallback(() => {
527
+ forceNullCursorRef.current = true;
516
528
  setPages([]);
517
529
  setCurrentCursor(null);
518
530
  setCurrentResult(null);
@@ -559,10 +571,12 @@ export const useInfiniteFeedByDevice = (deviceId, options) => {
559
571
  const prevDeviceIdRef = useRef(undefined);
560
572
  const lastAddedCursorRef = useRef(undefined);
561
573
  const pagesLengthRef = useRef(0);
574
+ const forceNullCursorRef = useRef(false);
575
+ const cursorForQuery = forceNullCursorRef.current ? null : currentCursor;
562
576
  const currentQuery = useGetFeedInfiniteByDeviceQuery({
563
577
  deviceId,
564
578
  limit: options.limit || 20,
565
- cursor: currentCursor,
579
+ cursor: cursorForQuery,
566
580
  supabase: options.supabase,
567
581
  }, { skip: !options.enabled || !deviceId });
568
582
  useEffect(() => {
@@ -572,6 +586,7 @@ export const useInfiniteFeedByDevice = (deviceId, options) => {
572
586
  useEffect(() => {
573
587
  if (prevDeviceIdRef.current !== undefined &&
574
588
  prevDeviceIdRef.current !== deviceId) {
589
+ forceNullCursorRef.current = true;
575
590
  setPages([]);
576
591
  setCurrentCursor(null);
577
592
  lastAddedCursorRef.current = undefined;
@@ -586,35 +601,39 @@ export const useInfiniteFeedByDevice = (deviceId, options) => {
586
601
  useEffect(() => {
587
602
  if (!currentQuery.data || currentQuery.isLoading)
588
603
  return;
604
+ const cursor = cursorForQuery;
605
+ if (cursor === null) {
606
+ forceNullCursorRef.current = false;
607
+ }
589
608
  if (pagesLengthRef.current > 0 &&
590
- feedCursorEq(lastAddedCursorRef.current ?? null, currentCursor))
609
+ feedCursorEq(lastAddedCursorRef.current ?? null, cursor))
591
610
  return;
592
- const nextResult = {
593
- hasMore: currentQuery.data?.hasMore ?? false,
594
- nextCursor: currentQuery.data?.nextCursor ?? null,
595
- };
596
- setLastResult(nextResult);
611
+ const items = Array.isArray(currentQuery.data?.items)
612
+ ? currentQuery.data.items
613
+ : [];
614
+ const limitForPage = options.limit || 20;
615
+ const nextCursor = currentQuery.data?.nextCursor ?? null;
616
+ const hasMore = (items.length >= limitForPage ||
617
+ (items.length > 0 && items.length < limitForPage)) &&
618
+ nextCursor != null;
619
+ setLastResult({ hasMore, nextCursor });
597
620
  setPages((prev) => {
598
- const existingPage = prev.find((p) => feedCursorEq(p.cursor, currentCursor));
621
+ const existingPage = prev.find((p) => feedCursorEq(p.cursor, cursor));
599
622
  if (!existingPage) {
600
- const items = Array.isArray(currentQuery.data?.items)
601
- ? currentQuery.data.items
602
- : [];
603
- const limit = options.limit || 20;
604
- if (items.length >= limit) {
605
- lastAddedCursorRef.current = currentCursor;
623
+ if (items.length >= limitForPage) {
624
+ lastAddedCursorRef.current = cursor;
606
625
  }
607
626
  return [
608
627
  ...prev,
609
- { cursor: currentCursor, data: items },
628
+ { cursor, data: items },
610
629
  ];
611
630
  }
612
631
  return prev;
613
632
  });
614
- if (nextResult.hasMore && nextResult.nextCursor != null) {
615
- setCurrentCursor(nextResult.nextCursor);
633
+ if (hasMore && nextCursor != null) {
634
+ setCurrentCursor(nextCursor);
616
635
  }
617
- }, [currentQuery.data, currentQuery.isLoading, currentCursor, pages.length, options.limit]);
636
+ }, [currentQuery.data, currentQuery.isLoading, cursorForQuery, pages.length, options.limit]);
618
637
  const loadMore = useCallback(() => {
619
638
  if (lastResult?.hasMore &&
620
639
  lastResult.nextCursor != null &&
@@ -623,6 +642,7 @@ export const useInfiniteFeedByDevice = (deviceId, options) => {
623
642
  }
624
643
  }, [lastResult, currentQuery.isLoading]);
625
644
  const refetch = useCallback(() => {
645
+ forceNullCursorRef.current = true;
626
646
  setPages([]);
627
647
  setCurrentCursor(null);
628
648
  lastAddedCursorRef.current = undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "1.4.32",
3
+ "version": "1.4.33",
4
4
  "description": "Core utilities and helpers for Adventure Labs Scout applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",