From 260b1671343a11e52a2cd1cddd680f513609206f Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 7 Jul 2026 18:39:34 +0200 Subject: [PATCH] fix: context menu refresh stays open with loading spinner, cache age reacts to refetch * fix: context menu refresh stays open with loading spinner, cache age reacts to refetch * style: auto-fix lint and format * refactor: pass isFetching as prop to WidgetCacheAge to avoid duplicate useIsFetching --------- Co-authored-by: ajnart --- .../board/items/widget-context-menu.tsx | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/apps/nextjs/src/components/board/items/widget-context-menu.tsx b/apps/nextjs/src/components/board/items/widget-context-menu.tsx index 415151a52..c3f3469cb 100644 --- a/apps/nextjs/src/components/board/items/widget-context-menu.tsx +++ b/apps/nextjs/src/components/board/items/widget-context-menu.tsx @@ -2,10 +2,10 @@ import type { MutableRefObject, ReactNode } from "react"; import { useCallback, useEffect, useMemo, useState } from "react"; -import { Group, Menu, Switch, Text } from "@mantine/core"; +import { Group, Loader, Menu, Switch, Text } from "@mantine/core"; import { IconCopy, IconLayoutKanban, IconRefresh, IconSettings, IconTrash } from "@tabler/icons-react"; import type { QueryClient } from "@tanstack/react-query"; -import { useQueryClient } from "@tanstack/react-query"; +import { useIsFetching, useQueryClient } from "@tanstack/react-query"; import { clientApi } from "@homarr/api/client"; import { useSession } from "@homarr/auth/client"; @@ -50,8 +50,9 @@ export const WidgetContextMenu = ({ item, widgetStateRef, children }: WidgetCont const queryClient = useQueryClient(); const widgetQueryKey = useMemo(() => [["widget", item.kind]], [item.kind]); + const isWidgetFetching = useIsFetching({ queryKey: widgetQueryKey }) > 0; const handleRefetch = useCallback(() => { - void queryClient.invalidateQueries({ queryKey: widgetQueryKey }); + void queryClient.refetchQueries({ queryKey: widgetQueryKey, type: "all" }); }, [queryClient, widgetQueryKey]); const options = useMemo( @@ -223,11 +224,15 @@ export const WidgetContextMenu = ({ item, widgetStateRef, children }: WidgetCont <> {(toggleOptions.length > 0 || visibleWidgetActions.length > 0 || isEditMode) && } - } onClick={handleRefetch}> + : } + onClick={handleRefetch} + disabled={isWidgetFetching} + > {tMenu("refresh")} - + @@ -265,13 +270,24 @@ export const WidgetContextMenu = ({ item, widgetStateRef, children }: WidgetCont ); }; -const WidgetCacheAge = ({ queryClient, queryKey }: { queryClient: QueryClient; queryKey: unknown[] }) => { +const WidgetCacheAge = ({ + queryClient, + queryKey, + isFetching, +}: { + queryClient: QueryClient; + queryKey: unknown[]; + isFetching: boolean; +}) => { + // 1s tick for "just now" → "1s ago" label transition; isFetching prop handles refetch reactivity const [, setTick] = useState(0); useEffect(() => { const id = setInterval(() => setTick((n) => n + 1), 1000); return () => clearInterval(id); }, []); + if (isFetching) return ; + const timestamps = queryClient .getQueryCache() .findAll({ queryKey })