Skip to content

Commit 3f05e80

Browse files
mikeldkinggithub-actions[bot]claudecephalization
authored
feat(ui): refresh projects page on mount and on 60s interval (#12694)
* feat(ui): refresh projects page on mount and on interval Fixes #12431. The projects list now fetches fresh data on page mount using `store-and-network` policy and polls every 60 seconds so new projects appear while the user is idle. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * format * Pause useInterval when page is no longer visible --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Tony Powell <apowell@arize.com>
1 parent 465ff59 commit 3f05e80

2 files changed

Lines changed: 40 additions & 12 deletions

File tree

app/src/hooks/useInterval.ts

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,51 @@ import { useEffect, useRef } from "react";
33
type Callback = () => void;
44

55
/**
6-
* Custom hook to use setInterval with React hooks
7-
* @param callback
8-
* @param {number | null} delay - if set to null, no interval will be set
6+
* setInterval that automatically pauses when the page tab is hidden
7+
* and fires immediately upon becoming visible again.
8+
* @param callback - function to call on each tick
9+
* @param delay - interval in ms, or null to disable
910
*/
1011
export function useInterval(callback: Callback, delay: number | null) {
1112
const savedCallback = useRef<Callback | null>(null);
1213

13-
// Remember the latest callback.
1414
useEffect(() => {
1515
savedCallback.current = callback;
1616
}, [callback]);
1717

18-
// Set up the interval.
1918
useEffect(() => {
19+
if (typeof delay !== "number") return;
20+
21+
const intervalMs = delay;
22+
2023
function tick() {
21-
if (savedCallback.current) {
22-
savedCallback.current();
23-
}
24+
savedCallback.current?.();
2425
}
25-
if (typeof delay === "number") {
26-
const id = setInterval(tick, delay);
27-
return () => clearInterval(id);
26+
27+
let id: ReturnType<typeof setInterval> | null = setInterval(
28+
tick,
29+
intervalMs
30+
);
31+
32+
function onVisibilityChange() {
33+
if (document.visibilityState === "hidden") {
34+
if (id != null) {
35+
clearInterval(id);
36+
id = null;
37+
}
38+
} else {
39+
if (id == null) {
40+
tick();
41+
id = setInterval(tick, intervalMs);
42+
}
43+
}
2844
}
45+
46+
document.addEventListener("visibilitychange", onVisibilityChange);
47+
48+
return () => {
49+
if (id != null) clearInterval(id);
50+
document.removeEventListener("visibilitychange", onVisibilityChange);
51+
};
2952
}, [delay]);
3053
}

app/src/pages/projects/ProjectsPage.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import {
6060
usePreferencesContext,
6161
useViewerCanModify,
6262
} from "@phoenix/contexts";
63+
import { useInterval } from "@phoenix/hooks";
6364
import type {
6465
ProjectsPageProjectMetricsQuery,
6566
ProjectsPageProjectMetricsQuery$data,
@@ -81,6 +82,7 @@ import { NewProjectButton } from "./NewProjectButton";
8182
import { ProjectActionMenu } from "./ProjectActionMenu";
8283

8384
const PAGE_SIZE = 10;
85+
const PROJECTS_POLL_INTERVAL_MS = 60_000;
8486

8587
const useProjectSortQueryParams = () => {
8688
const { projectSortOrder } = usePreferencesContext((state) => ({
@@ -120,7 +122,8 @@ export function ProjectsPage() {
120122
first: PAGE_SIZE,
121123
filter: { value: "", col: "name" },
122124
...queryParams,
123-
}
125+
},
126+
{ fetchPolicy: "store-and-network" }
124127
);
125128

126129
return (
@@ -223,6 +226,8 @@ export function ProjectsPageContent({
223226
[_refetch, queryArgs]
224227
);
225228

229+
useInterval(() => refetch({}), PROJECTS_POLL_INTERVAL_MS);
230+
226231
const projects = projectsData?.projects.edges.map((p) => p.project);
227232

228233
const projectsContainerRef = useRef<HTMLDivElement>(null);

0 commit comments

Comments
 (0)