Merge pull request #33432 from backstage/freben/fix-catalog-filter-flicker
Fix catalog filter flicker caused by stale render cycle
This commit is contained in:
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Fixed the catalog table briefly showing an empty loading state when changing filters. The table now keeps displaying stale results until new data arrives.
|
||||
@@ -454,30 +454,36 @@ export const EntityListProvider = <EntityFilters extends DefaultEntityFilters>(
|
||||
[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 = <EntityFilters extends DefaultEntityFilters>(
|
||||
paginationMode,
|
||||
}),
|
||||
[
|
||||
outputState,
|
||||
latestOutput,
|
||||
updateFilters,
|
||||
queryParameters,
|
||||
loading,
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
useEntityList,
|
||||
useStarredEntities,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { visuallyHidden } from '@mui/utils';
|
||||
import Edit from '@material-ui/icons/Edit';
|
||||
@@ -108,6 +109,14 @@ export const CatalogTable = (props: CatalogTableProps) => {
|
||||
paginationMode,
|
||||
} = entityListContext;
|
||||
|
||||
// For non-paginated tables, only show the full loading indicator when
|
||||
// there's no data yet (initial load). During filter changes we keep stale
|
||||
// data visible and let the new results swap in seamlessly. For paginated
|
||||
// tables we always show loading, since stale data from a different page
|
||||
// would be misleading.
|
||||
const isLoading =
|
||||
paginationMode === 'none' ? loading && entities.length === 0 : loading;
|
||||
|
||||
const tableColumns = useMemo(
|
||||
() =>
|
||||
typeof columns === 'function' ? columns(entityListContext) : columns,
|
||||
@@ -189,17 +198,28 @@ export const CatalogTable = (props: CatalogTableProps) => {
|
||||
const titlePreamble = capitalize(
|
||||
filters.user?.value ?? t('catalogTable.allFilters'),
|
||||
);
|
||||
const title =
|
||||
const titleText =
|
||||
props.title ||
|
||||
[titlePreamble, currentType, pluralize(currentKind), currentCount]
|
||||
.filter(s => s)
|
||||
.join(' ');
|
||||
const title =
|
||||
loading && !isLoading ? (
|
||||
<span
|
||||
style={{ display: 'inline-flex', alignItems: 'center', gap: '0.5em' }}
|
||||
>
|
||||
{titleText}
|
||||
<CircularProgress size="0.8em" data-testid="loading-indicator" />
|
||||
</span>
|
||||
) : (
|
||||
titleText
|
||||
);
|
||||
|
||||
const actions = props.actions || defaultActions;
|
||||
const options: TableProps['options'] = {
|
||||
actionsColumnIndex: -1,
|
||||
loadingType: 'linear' as const,
|
||||
showEmptyDataSourceMessage: !loading,
|
||||
showEmptyDataSourceMessage: !isLoading,
|
||||
padding: 'dense' as const,
|
||||
...tableOptions,
|
||||
};
|
||||
@@ -209,7 +229,7 @@ export const CatalogTable = (props: CatalogTableProps) => {
|
||||
<CursorPaginatedCatalogTable
|
||||
columns={tableColumns}
|
||||
emptyContent={emptyContent}
|
||||
isLoading={loading}
|
||||
isLoading={isLoading}
|
||||
title={title}
|
||||
actions={actions}
|
||||
subtitle={subtitle}
|
||||
@@ -224,7 +244,7 @@ export const CatalogTable = (props: CatalogTableProps) => {
|
||||
<OffsetPaginatedCatalogTable
|
||||
columns={tableColumns}
|
||||
emptyContent={emptyContent}
|
||||
isLoading={loading}
|
||||
isLoading={isLoading}
|
||||
title={title}
|
||||
actions={actions}
|
||||
subtitle={subtitle}
|
||||
@@ -240,7 +260,7 @@ export const CatalogTable = (props: CatalogTableProps) => {
|
||||
|
||||
return (
|
||||
<Table<CatalogTableRow>
|
||||
isLoading={loading}
|
||||
isLoading={isLoading}
|
||||
columns={tableColumns}
|
||||
options={{
|
||||
paging: showPagination,
|
||||
|
||||
Reference in New Issue
Block a user