From 51aacae34b398fed4b7de6f4a6c3efcbcdde5587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 19 Mar 2026 09:49:05 +0100 Subject: [PATCH] Fix catalog filter flicker caused by stale render cycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EntityListProvider exposed entities from `outputState` (synced via useEffect) but `loading` from `useAsyncFn`. When the async function resolved, `loading` flipped to false one render before `outputState` updated, causing a flash of stale data between the loading indicator and the new results. Use `resolvedValue` directly in the context value when available, eliminating the extra render lag. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Fredrik Adelöw --- .changeset/fix-catalog-filter-flicker.md | 5 +++++ .../src/hooks/useEntityListProvider.tsx | 22 ++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 .changeset/fix-catalog-filter-flicker.md diff --git a/.changeset/fix-catalog-filter-flicker.md b/.changeset/fix-catalog-filter-flicker.md new file mode 100644 index 0000000000..5964f7ada9 --- /dev/null +++ b/.changeset/fix-catalog-filter-flicker.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Fixed a UI flicker in the catalog entity list where changing a filter would briefly flash stale data before showing the new results. diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index 12eb9dff6c..a18575befa 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -454,30 +454,36 @@ export const EntityListProvider = ( [paginationMode], ); + // Use resolvedValue directly when available to avoid an extra render cycle. + // Without this, there's a render where loading has flipped back to false but + // outputState hasn't been updated yet (it syncs via useEffect), causing a + // flash of stale data between the loading state and the new results. + const latestOutput = resolvedValue ?? outputState; + const pageInfo = useMemo(() => { if (paginationMode !== 'cursor') { return undefined; } - const prevCursor = outputState.pageInfo?.prevCursor; - const nextCursor = outputState.pageInfo?.nextCursor; + const prevCursor = latestOutput.pageInfo?.prevCursor; + const nextCursor = latestOutput.pageInfo?.nextCursor; return { prev: prevCursor ? () => setCursor(prevCursor) : undefined, next: nextCursor ? () => setCursor(nextCursor) : undefined, }; - }, [paginationMode, outputState.pageInfo]); + }, [paginationMode, latestOutput.pageInfo]); const value = useMemo( () => ({ - filters: outputState.appliedFilters, - entities: outputState.entities, - backendEntities: outputState.backendEntities, + filters: latestOutput.appliedFilters, + entities: latestOutput.entities, + backendEntities: latestOutput.backendEntities, updateFilters, queryParameters, loading, error, pageInfo, - totalItems: outputState.totalItems, + totalItems: latestOutput.totalItems, limit, offset, setLimit, @@ -485,7 +491,7 @@ export const EntityListProvider = ( paginationMode, }), [ - outputState, + latestOutput, updateFilters, queryParameters, loading,