diff --git a/client/src/app/components/InfiniteScroller/InfiniteScroller.css b/client/src/app/components/InfiniteScroller/InfiniteScroller.css
index cd387bb2f..6d8316199 100644
--- a/client/src/app/components/InfiniteScroller/InfiniteScroller.css
+++ b/client/src/app/components/InfiniteScroller/InfiniteScroller.css
@@ -4,19 +4,3 @@
font-style: italic;
font-weight: bold;
}
-
-.infinite-scroll-visible-zone {
- position: fixed;
- top: 0;
- left: 0;
- bottom: 10px;
- width: 100%;
-}
-
-.infinite-scroll-hidden-zone {
- position: fixed;
- bottom: 0;
- left: 0;
- width: 100%;
- height: 10px;
-}
diff --git a/client/src/app/components/InfiniteScroller/InfiniteScroller.tsx b/client/src/app/components/InfiniteScroller/InfiniteScroller.tsx
index ebec58626..972c3b87d 100644
--- a/client/src/app/components/InfiniteScroller/InfiniteScroller.tsx
+++ b/client/src/app/components/InfiniteScroller/InfiniteScroller.tsx
@@ -1,50 +1,56 @@
-import React, { ReactNode, useEffect } from "react";
+import React, { ReactNode, useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import { useVisibilityTracker } from "./useVisibilityTracker";
import "./InfiniteScroller.css";
export interface InfiniteScrollerProps {
children: ReactNode;
- className?: string;
- fetchMore: () => void;
+ fetchMore: () => boolean;
hasMore: boolean;
- isReadyToFetch: boolean;
+ itemCount: number;
}
export const InfiniteScroller = ({
children,
- className,
fetchMore,
hasMore,
- isReadyToFetch,
+ itemCount,
}: InfiniteScrollerProps) => {
const { t } = useTranslation();
- const {
- isVisible,
- loaderRef: sentinelRef,
- visibleZoneRef,
- hiddenZoneRef,
- } = useVisibilityTracker({
- enable: hasMore,
- });
+ // track how many items were known at time of triggering the fetch
+ // parent is expected to display empty state until some items are available
+ // initializing with zero ensures that the effect will be triggered immediately
+ const itemCountRef = useRef(0);
+ const { visible: isSentinelVisible, nodeRef: sentinelRef } =
+ useVisibilityTracker({
+ enable: hasMore,
+ });
+ console.log("infinite props ", hasMore, itemCount, itemCountRef.current);
useEffect(() => {
- if (isVisible && isReadyToFetch) {
- fetchMore();
+ console.log(
+ `infinite [visible= >${isSentinelVisible}<] `,
+ itemCount,
+ itemCountRef.current
+ );
+ if (
+ isSentinelVisible &&
+ itemCountRef.current !== itemCount &&
+ fetchMore()
+ ) {
+ itemCountRef.current = itemCount;
+ } else if (isSentinelVisible && itemCountRef.current === itemCount) {
+ // network call may fail which would block fetching
+ // TODO: implement reset based on hit counter i.e.
+ // if (hitCounter > maxHits) itemCountRef.current = 0
}
- console.log("infinite", isVisible);
- }, [isVisible, fetchMore]);
+ }, [isSentinelVisible, fetchMore, itemCount]);
return (
-
-
-
+
{children}
{hasMore && (
-
+
{t("message.loadingTripleDot")}
)}
diff --git a/client/src/app/components/InfiniteScroller/useVisibilityTracker.tsx b/client/src/app/components/InfiniteScroller/useVisibilityTracker.tsx
index dc1ab1579..81e0cd933 100644
--- a/client/src/app/components/InfiniteScroller/useVisibilityTracker.tsx
+++ b/client/src/app/components/InfiniteScroller/useVisibilityTracker.tsx
@@ -1,50 +1,40 @@
import { useEffect, useRef, useState } from "react";
-const intersectionCallback =
- (stateCallback: () => void) => (entries: IntersectionObserverEntry[]) => {
- entries.forEach(({ isIntersecting }) => {
- if (isIntersecting) {
- stateCallback();
- }
- });
- };
-
export function useVisibilityTracker({ enable }: { enable: boolean }) {
- const loaderRef = useRef
(null);
- const visibleZoneRef = useRef(null);
- const hiddenZoneRef = useRef(null);
- const [isVisible, setIsVisible] = useState(false);
+ const nodeRef = useRef(null);
+ const [visible, setVisible] = useState(false);
+ const node = nodeRef.current;
useEffect(() => {
- if (
- !enable ||
- !loaderRef.current ||
- !visibleZoneRef.current ||
- !hiddenZoneRef.current
- ) {
+ if (!enable || !node) {
console.log("useVisibilityTracker - disabled");
return undefined;
}
- const visibleZoneObserver = new IntersectionObserver(
- intersectionCallback(() => setIsVisible(true)),
- { root: visibleZoneRef.current, rootMargin: "0px", threshold: 1.0 }
- );
- const hiddenZoneObserver = new IntersectionObserver(
- intersectionCallback(() => setIsVisible(false)),
- { root: hiddenZoneRef.current, rootMargin: "0px", threshold: 1.0 }
+ // observer with default options - the whole view port used
+ // using a parent is hard
+ const observer = new IntersectionObserver(
+ (entries: IntersectionObserverEntry[]) =>
+ entries.forEach(({ isIntersecting, ...rest }) => {
+ if (isIntersecting) {
+ setVisible(true);
+ console.log("useVisibilityTracker - intersection", rest);
+ } else {
+ setVisible(false);
+ console.log("useVisibilityTracker - out-of-box", rest);
+ }
+ })
);
- visibleZoneObserver.observe(loaderRef.current);
- hiddenZoneObserver.observe(loaderRef.current);
+ observer.observe(node);
console.log("useVisibilityTracker - observe");
return () => {
- visibleZoneObserver.disconnect();
- hiddenZoneObserver.disconnect();
+ observer.disconnect();
+ setVisible(false);
console.log("useVisibilityTracker - disconnect");
};
- }, [enable]);
+ }, [enable, node]);
- return { isVisible, loaderRef, visibleZoneRef, hiddenZoneRef };
+ return { visible, nodeRef };
}
diff --git a/client/src/app/components/task-manager/TaskManagerDrawer.tsx b/client/src/app/components/task-manager/TaskManagerDrawer.tsx
index ef0e61096..f4489fe28 100644
--- a/client/src/app/components/task-manager/TaskManagerDrawer.tsx
+++ b/client/src/app/components/task-manager/TaskManagerDrawer.tsx
@@ -5,13 +5,6 @@ import {
Dropdown,
DropdownItem,
DropdownList,
- EmptyState,
- EmptyStateActions,
- EmptyStateBody,
- EmptyStateFooter,
- EmptyStateHeader,
- EmptyStateIcon,
- EmptyStateVariant,
MenuToggle,
MenuToggleElement,
NotificationDrawer,
@@ -22,8 +15,15 @@ import {
NotificationDrawerListItemBody,
NotificationDrawerListItemHeader,
Tooltip,
+ EmptyState,
+ EmptyStateHeader,
+ EmptyStateIcon,
+ EmptyStateBody,
+ EmptyStateVariant,
+ EmptyStateFooter,
+ EmptyStateActions,
} from "@patternfly/react-core";
-import { CubesIcon, EllipsisVIcon } from "@patternfly/react-icons";
+import { EllipsisVIcon, CubesIcon } from "@patternfly/react-icons";
import { css } from "@patternfly/react-styles";
import { Task, TaskState } from "@app/api/models";
@@ -81,6 +81,7 @@ export const TaskManagerDrawer: React.FC = forwardRef(
setExpandedItems([]);
};
+ console.log("tasks", tasks?.length);
return (
= forwardRef(
{tasks.map((task) => (
@@ -291,8 +292,14 @@ const useTaskManagerData = () => {
);
const fetchMore = useCallback(() => {
+ // forced fetch is not allowed when background fetch or other forced fetch is in progress
if (!isFetching && !isFetchingNextPage) {
fetchNextPage();
+ console.log("fetchMore - started");
+ return true;
+ } else {
+ console.log("fetchMore - blocked");
+ return false;
}
}, [isFetching, isFetchingNextPage, fetchNextPage]);
diff --git a/client/src/app/queries/tasks.ts b/client/src/app/queries/tasks.ts
index 036314975..81b70ef4d 100644
--- a/client/src/app/queries/tasks.ts
+++ b/client/src/app/queries/tasks.ts
@@ -94,6 +94,8 @@ export const useInfiniteServerTasks = (
refetchInterval?: number
) => {
return useInfiniteQuery({
+ // usually the params are part of the key
+ // infinite query tracks the actual params for all pages under one key
// eslint-disable-next-line @tanstack/query/exhaustive-deps
queryKey: [TasksQueryKey],
queryFn: async ({ pageParam = initialParams }) =>