Compare commits

..

1 Commits

Author SHA1 Message Date
Claude 8ce4627121 chore: upgrade react-router to 8.3.0
Patch/minor bump within the existing ^8 range. Includes the RFC 3986
path-segment encoding change, optional-segment param extraction fix and
route ranking fix for dynamic params with static suffixes.

Lockfile also picks up incidental transitive drift (acorn, fs-extra,
jsonfile, @jridgewell/trace-mapping) from re-resolution.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1tN6RYPVBX7FxxbnuqxC2
2026-08-02 07:59:12 +00:00
18 changed files with 113 additions and 161 deletions
+1 -1
View File
@@ -30,7 +30,7 @@
"react-fast-compare": "^3.2.2", "react-fast-compare": "^3.2.2",
"react-hook-form": "^7.80.0", "react-hook-form": "^7.80.0",
"react-icons": "5.6.0", "react-icons": "5.6.0",
"react-router": "^8.0.1", "react-router": "^8.3.0",
"react-virtuoso": "^4.18.7", "react-virtuoso": "^4.18.7",
"zustand": "^5.0.14" "zustand": "^5.0.14"
}, },
@@ -8,12 +8,12 @@ import { getCustomFields } from '../api/customFields';
const placeholder: CustomFields = {}; const placeholder: CustomFields = {};
export default function useCustomFields() { export default function useCustomFields() {
const { data, status, isFetching, isError, isLoadingError, refetch } = useQuery({ const { data, status, isFetching, isError, refetch } = useQuery({
queryKey: CUSTOM_FIELDS, queryKey: CUSTOM_FIELDS,
queryFn: ({ signal }) => getCustomFields({ signal }), queryFn: ({ signal }) => getCustomFields({ signal }),
placeholderData: (previousData, _previousQuery) => previousData, placeholderData: (previousData, _previousQuery) => previousData,
refetchInterval: queryRefetchIntervalSlow, refetchInterval: queryRefetchIntervalSlow,
}); });
return { data: data ?? placeholder, status, isFetching, isError, isLoadingError, refetch }; return { data: data ?? placeholder, status, isFetching, isError, refetch };
} }
@@ -6,14 +6,14 @@ import { getProjectData, postProjectData } from '../api/project';
import { projectDataPlaceholder } from '../models/ProjectData'; import { projectDataPlaceholder } from '../models/ProjectData';
export default function useProjectData() { export default function useProjectData() {
const { data, status, isFetching, isError, isLoadingError, refetch } = useQuery({ const { data, status, isFetching, isError, refetch } = useQuery({
queryKey: PROJECT_DATA, queryKey: PROJECT_DATA,
queryFn: ({ signal }) => getProjectData({ signal }), queryFn: ({ signal }) => getProjectData({ signal }),
placeholderData: (previousData, _previousQuery) => previousData, placeholderData: (previousData, _previousQuery) => previousData,
refetchInterval: queryRefetchIntervalSlow, refetchInterval: queryRefetchIntervalSlow,
}); });
return { data: data ?? projectDataPlaceholder, status, isFetching, isError, isLoadingError, refetch }; return { data: data ?? projectDataPlaceholder, status, isFetching, isError, refetch };
} }
export function useUpdateProjectData() { export function useUpdateProjectData() {
@@ -32,7 +32,7 @@ export default function useRundown() {
data: { loaded: loadedRundownId }, data: { loaded: loadedRundownId },
} = useProjectRundowns(); } = useProjectRundowns();
const { data, status, isError, isLoadingError, refetch, isFetching } = useQuery<Rundown>({ const { data, status, isError, refetch, isFetching } = useQuery<Rundown>({
queryKey: loadedRundownId ? getRundownQueryKey(loadedRundownId) : CURRENT_RUNDOWN_QUERY_KEY, queryKey: loadedRundownId ? getRundownQueryKey(loadedRundownId) : CURRENT_RUNDOWN_QUERY_KEY,
queryFn: ({ signal }) => fetchCurrentRundown({ signal }), queryFn: ({ signal }) => fetchCurrentRundown({ signal }),
refetchInterval: queryRefetchIntervalSlow, refetchInterval: queryRefetchIntervalSlow,
@@ -50,14 +50,14 @@ export default function useRundown() {
queryClient.removeQueries({ queryKey: CURRENT_RUNDOWN_QUERY_KEY, exact: true }); queryClient.removeQueries({ queryKey: CURRENT_RUNDOWN_QUERY_KEY, exact: true });
}, [loadedRundownId, queryClient]); }, [loadedRundownId, queryClient]);
return { data: data ?? cachedRundownPlaceholder, status, isError, isLoadingError, refetch, isFetching }; return { data: data ?? cachedRundownPlaceholder, status, isError, refetch, isFetching };
} }
export function useRundownWithMetadata() { export function useRundownWithMetadata() {
const { data, status, isLoadingError } = useRundown(); const { data, status } = useRundown();
const selectedEventId = useSelectedEventId(); const selectedEventId = useSelectedEventId();
const rundownMetadata = useMemo(() => getRundownMetadata(data, selectedEventId), [data, selectedEventId]); const rundownMetadata = useMemo(() => getRundownMetadata(data, selectedEventId), [data, selectedEventId]);
return { data, status, isLoadingError, rundownMetadata }; return { data, status, rundownMetadata };
} }
/** /**
@@ -65,7 +65,7 @@ export function useRundownWithMetadata() {
* built from the order and rundown fields * built from the order and rundown fields
*/ */
export function useFlatRundown() { export function useFlatRundown() {
const { data, status, isLoadingError } = useRundown(); const { data, status } = useRundown();
const flatRundown = useMemo(() => { const flatRundown = useMemo(() => {
if (data.revision === -1) { if (data.revision === -1) {
@@ -74,15 +74,15 @@ export function useFlatRundown() {
return data.flatOrder.map((id) => data.entries[id]).filter((entry): entry is OntimeEntry => entry !== undefined); return data.flatOrder.map((id) => data.entries[id]).filter((entry): entry is OntimeEntry => entry !== undefined);
}, [data]); }, [data]);
return { data: flatRundown, rundownId: data.id, status, isLoadingError }; return { data: flatRundown, rundownId: data.id, status };
} }
export function useFlatRundownWithMetadata() { export function useFlatRundownWithMetadata() {
const { data, status, isLoadingError } = useRundown(); const { data, status } = useRundown();
const selectedEventId = useSelectedEventId(); const selectedEventId = useSelectedEventId();
const rundownWithMetadata = useMemo(() => getFlatRundownMetadata(data, selectedEventId), [data, selectedEventId]); const rundownWithMetadata = useMemo(() => getFlatRundownMetadata(data, selectedEventId), [data, selectedEventId]);
return { data: rundownWithMetadata, status, isLoadingError }; return { data: rundownWithMetadata, status };
} }
/** /**
@@ -127,7 +127,7 @@ export function useRundownAuxData() {
export function useRundownById(rundownId: string | null | undefined) { export function useRundownById(rundownId: string | null | undefined) {
const enabled = Boolean(rundownId); const enabled = Boolean(rundownId);
const { data, status, isError, isLoadingError, refetch, isFetching } = useQuery<Rundown>({ const { data, status, isError, refetch, isFetching } = useQuery<Rundown>({
queryKey: getRundownQueryKey(rundownId ?? ''), queryKey: getRundownQueryKey(rundownId ?? ''),
queryFn: ({ signal }) => fetchRundown(rundownId!, { signal }), queryFn: ({ signal }) => fetchRundown(rundownId!, { signal }),
enabled, enabled,
@@ -135,5 +135,5 @@ export function useRundownById(rundownId: string | null | undefined) {
refetchInterval: queryRefetchIntervalSlow, refetchInterval: queryRefetchIntervalSlow,
}); });
return { data: data ?? cachedRundownPlaceholder, status, isError, isLoadingError, refetch, isFetching }; return { data: data ?? cachedRundownPlaceholder, status, isError, refetch, isFetching };
} }
@@ -11,7 +11,6 @@ export type RundownSource = {
rundown: Rundown; rundown: Rundown;
flatRundown: ExtendedEntry[]; flatRundown: ExtendedEntry[];
status: string; status: string;
isLoadingError: boolean;
selectedEventId: EntryId | null; selectedEventId: EntryId | null;
}; };
@@ -36,7 +35,7 @@ function useRundownSource(rundownId: string | null, loadedRundownId: string | nu
const isLoadedTarget = rundownId !== null && rundownId === loadedRundownId; const isLoadedTarget = rundownId !== null && rundownId === loadedRundownId;
const runtimeSelectedEventId = useSelectedEventId(); const runtimeSelectedEventId = useSelectedEventId();
const effectiveSelectedEventId = isLoadedTarget ? runtimeSelectedEventId : null; const effectiveSelectedEventId = isLoadedTarget ? runtimeSelectedEventId : null;
const { data: rundown, status, isLoadingError } = useRundownById(rundownId); const { data: rundown, status } = useRundownById(rundownId);
const flatRundown = useMemo( const flatRundown = useMemo(
() => getFlatRundownMetadata(rundown, effectiveSelectedEventId), () => getFlatRundownMetadata(rundown, effectiveSelectedEventId),
[effectiveSelectedEventId, rundown], [effectiveSelectedEventId, rundown],
@@ -48,9 +47,8 @@ function useRundownSource(rundownId: string | null, loadedRundownId: string | nu
rundown, rundown,
flatRundown, flatRundown,
status, status,
isLoadingError,
selectedEventId: effectiveSelectedEventId, selectedEventId: effectiveSelectedEventId,
}), }),
[effectiveSelectedEventId, flatRundown, isLoadingError, rundown, rundownId, status], [effectiveSelectedEventId, flatRundown, rundown, rundownId, status],
); );
} }
@@ -6,7 +6,7 @@ import { getSettings } from '../api/settings';
import { ontimePlaceholderSettings } from '../models/OntimeSettings'; import { ontimePlaceholderSettings } from '../models/OntimeSettings';
export default function useSettings() { export default function useSettings() {
const { data, status, isFetching, isError, isLoadingError, refetch } = useQuery({ const { data, status, isFetching, isError, refetch } = useQuery({
queryKey: APP_SETTINGS, queryKey: APP_SETTINGS,
queryFn: ({ signal }) => getSettings({ signal }), queryFn: ({ signal }) => getSettings({ signal }),
placeholderData: (previousData, _previousQuery) => previousData, placeholderData: (previousData, _previousQuery) => previousData,
@@ -22,5 +22,5 @@ export default function useSettings() {
}, },
}); });
return { data: data ?? ontimePlaceholderSettings, status, isFetching, isError, isLoadingError, refetch }; return { data: data ?? ontimePlaceholderSettings, status, isFetching, isError, refetch };
} }
@@ -7,7 +7,7 @@ import { VIEW_SETTINGS } from '../api/constants';
import { viewsSettingsPlaceholder } from '../models/ViewSettings.type'; import { viewsSettingsPlaceholder } from '../models/ViewSettings.type';
export default function useViewSettings() { export default function useViewSettings() {
const { data, status, isLoadingError } = useQuery({ const { data, status } = useQuery({
queryKey: VIEW_SETTINGS, queryKey: VIEW_SETTINGS,
queryFn: ({ signal }) => getViewSettings({ signal }), queryFn: ({ signal }) => getViewSettings({ signal }),
placeholderData: (previousData, _previousQuery) => previousData, placeholderData: (previousData, _previousQuery) => previousData,
@@ -24,5 +24,5 @@ export default function useViewSettings() {
}, },
}); });
return { data: data ?? viewsSettingsPlaceholder, status, isLoadingError, mutateAsync }; return { data: data ?? viewsSettingsPlaceholder, status, mutateAsync };
} }
@@ -14,18 +14,9 @@ export interface OperatorData {
} }
export function useOperatorData(): ViewData<OperatorData> { export function useOperatorData(): ViewData<OperatorData> {
const { const { data: rundown, rundownMetadata, status: rundownStatus } = useRundownWithMetadata();
data: rundown, const { data: customFields, status: customFieldStatus } = useCustomFields();
rundownMetadata, const { data: settings, status: settingsStatus } = useSettings();
status: rundownStatus,
isLoadingError: rundownIsLoadingError,
} = useRundownWithMetadata();
const {
data: customFields,
status: customFieldStatus,
isLoadingError: customFieldIsLoadingError,
} = useCustomFields();
const { data: settings, status: settingsStatus, isLoadingError: settingsIsLoadingError } = useSettings();
return { return {
data: { data: {
@@ -34,10 +25,6 @@ export function useOperatorData(): ViewData<OperatorData> {
customFields, customFields,
settings, settings,
}, },
status: aggregateQueryStatus([ status: aggregateQueryStatus([rundownStatus, customFieldStatus, settingsStatus]),
{ status: rundownStatus, isLoadingError: rundownIsLoadingError },
{ status: customFieldStatus, isLoadingError: customFieldIsLoadingError },
{ status: settingsStatus, isLoadingError: settingsIsLoadingError },
]),
}; };
} }
@@ -8,7 +8,7 @@ import Rundown from './Rundown';
export default memo(RundownList); export default memo(RundownList);
function RundownList() { function RundownList() {
const { data, status, isLoadingError, rundownMetadata } = useRundownWithMetadata(); const { data, status, rundownMetadata } = useRundownWithMetadata();
const featureData = useRundownEditor(); const featureData = useRundownEditor();
const { getLocalizedString } = useTranslation(); const { getLocalizedString } = useTranslation();
@@ -17,7 +17,7 @@ function RundownList() {
return <EmptyFill text='Loading…' />; return <EmptyFill text='Loading…' />;
} }
if (isLoadingError) { if (status === 'error') {
return <EmptyFill text={getLocalizedString('common.no_data')} />; return <EmptyFill text={getLocalizedString('common.no_data')} />;
} }
@@ -20,14 +20,10 @@ export function useBackstageData(): ViewData<BackstageData> {
const isMirrored = useViewOptionsStore((state) => state.mirror); const isMirrored = useViewOptionsStore((state) => state.mirror);
// HTTP API data // HTTP API data
const { data: rundownData, status: rundownStatus, isLoadingError: rundownIsLoadingError } = useFlatRundown(); const { data: rundownData, status: rundownStatus } = useFlatRundown();
const { data: projectData, status: projectDataStatus, isLoadingError: projectDataIsLoadingError } = useProjectData(); const { data: projectData, status: projectDataStatus } = useProjectData();
const { data: settings, status: settingsStatus, isLoadingError: settingsIsLoadingError } = useSettings(); const { data: settings, status: settingsStatus } = useSettings();
const { const { data: customFields, status: customFieldsStatus } = useCustomFields();
data: customFields,
status: customFieldsStatus,
isLoadingError: customFieldsIsLoadingError,
} = useCustomFields();
return { return {
data: { data: {
@@ -37,11 +33,6 @@ export function useBackstageData(): ViewData<BackstageData> {
isMirrored, isMirrored,
settings, settings,
}, },
status: aggregateQueryStatus([ status: aggregateQueryStatus([rundownStatus, projectDataStatus, settingsStatus, customFieldsStatus]),
{ status: rundownStatus, isLoadingError: rundownIsLoadingError },
{ status: projectDataStatus, isLoadingError: projectDataIsLoadingError },
{ status: settingsStatus, isLoadingError: settingsIsLoadingError },
{ status: customFieldsStatus, isLoadingError: customFieldsIsLoadingError },
]),
}; };
} }
@@ -21,18 +21,10 @@ export function useCountdownData(): ViewData<CountdownData> {
const isMirrored = useViewOptionsStore((state) => state.mirror); const isMirrored = useViewOptionsStore((state) => state.mirror);
// HTTP API data // HTTP API data
const { const { data: rundownData, status: rundownStatus } = useFlatRundownWithMetadata();
data: rundownData, const { data: projectData, status: projectDataStatus } = useProjectData();
status: rundownStatus, const { data: settings, status: settingsStatus } = useSettings();
isLoadingError: rundownIsLoadingError, const { data: customFields, status: customFieldsStatus } = useCustomFields();
} = useFlatRundownWithMetadata();
const { data: projectData, status: projectDataStatus, isLoadingError: projectDataIsLoadingError } = useProjectData();
const { data: settings, status: settingsStatus, isLoadingError: settingsIsLoadingError } = useSettings();
const {
data: customFields,
status: customFieldsStatus,
isLoadingError: customFieldsIsLoadingError,
} = useCustomFields();
return { return {
data: { data: {
@@ -42,11 +34,6 @@ export function useCountdownData(): ViewData<CountdownData> {
isMirrored, isMirrored,
settings, settings,
}, },
status: aggregateQueryStatus([ status: aggregateQueryStatus([rundownStatus, projectDataStatus, settingsStatus, customFieldsStatus]),
{ status: rundownStatus, isLoadingError: rundownIsLoadingError },
{ status: projectDataStatus, isLoadingError: projectDataIsLoadingError },
{ status: settingsStatus, isLoadingError: settingsIsLoadingError },
{ status: customFieldsStatus, isLoadingError: customFieldsIsLoadingError },
]),
}; };
} }
@@ -63,7 +63,7 @@ export default function CuesheetTable({
isCurrentRundown, isCurrentRundown,
insertElement, insertElement,
}: CuesheetTableProps) { }: CuesheetTableProps) {
const { flatRundown, status, isLoadingError, selectedEventId } = source; const { flatRundown, status, selectedEventId } = source;
const { updateEntry, updateTimer, addEntry } = useEntryActionsContext(); const { updateEntry, updateTimer, addEntry } = useEntryActionsContext();
const { getLocalizedString } = useTranslation(); const { getLocalizedString } = useTranslation();
const canCreateEntries = useCuesheetPermissions((state) => state.canCreateEntries) && cuesheetMode === AppMode.Edit; const canCreateEntries = useCuesheetPermissions((state) => state.canCreateEntries) && cuesheetMode === AppMode.Edit;
@@ -235,7 +235,7 @@ export default function CuesheetTable({
return <EmptyFill text='Loading…' className={style.tableLoading} />; return <EmptyFill text='Loading…' className={style.tableLoading} />;
} }
if (isLoadingError) { if (status === 'error') {
return <EmptyFill text={getLocalizedString('common.no_data')} className={style.tableLoading} />; return <EmptyFill text={getLocalizedString('common.no_data')} className={style.tableLoading} />;
} }
@@ -2,7 +2,7 @@ import { ProjectData } from 'ontime-types';
import useProjectData from '../../common/hooks-query/useProjectData'; import useProjectData from '../../common/hooks-query/useProjectData';
import { useViewOptionsStore } from '../../common/stores/viewOptions'; import { useViewOptionsStore } from '../../common/stores/viewOptions';
import { ViewData, aggregateQueryStatus } from '../utils/viewLoader.utils'; import { ViewData } from '../utils/viewLoader.utils';
export interface ProjectInfoData { export interface ProjectInfoData {
projectData: ProjectData; projectData: ProjectData;
@@ -14,13 +14,13 @@ export function useProjectInfoData(): ViewData<ProjectInfoData> {
const isMirrored = useViewOptionsStore((state) => state.mirror); const isMirrored = useViewOptionsStore((state) => state.mirror);
// HTTP API data // HTTP API data
const { data: projectData, status: projectDataStatus, isLoadingError: projectDataIsLoadingError } = useProjectData(); const { data: projectData, status: projectDataStatus } = useProjectData();
return { return {
data: { data: {
projectData, projectData,
isMirrored, isMirrored,
}, },
status: aggregateQueryStatus([{ status: projectDataStatus, isLoadingError: projectDataIsLoadingError }]), status: projectDataStatus,
}; };
} }
+5 -18
View File
@@ -20,18 +20,10 @@ export function useStudioData(): ViewData<StudioData> {
const isMirrored = useViewOptionsStore((state) => state.mirror); const isMirrored = useViewOptionsStore((state) => state.mirror);
// HTTP API data // HTTP API data
const { data: projectData, status: projectDataStatus, isLoadingError: projectDataIsLoadingError } = useProjectData(); const { data: projectData, status: projectDataStatus } = useProjectData();
const { const { data: viewSettings, status: viewSettingsStatus } = useViewSettings();
data: viewSettings, const { data: settings, status: settingsStatus } = useSettings();
status: viewSettingsStatus, const { data: customFields, status: customFieldsStatus } = useCustomFields();
isLoadingError: viewSettingsIsLoadingError,
} = useViewSettings();
const { data: settings, status: settingsStatus, isLoadingError: settingsIsLoadingError } = useSettings();
const {
data: customFields,
status: customFieldsStatus,
isLoadingError: customFieldsIsLoadingError,
} = useCustomFields();
return { return {
data: { data: {
@@ -41,11 +33,6 @@ export function useStudioData(): ViewData<StudioData> {
settings, settings,
viewSettings, viewSettings,
}, },
status: aggregateQueryStatus([ status: aggregateQueryStatus([projectDataStatus, viewSettingsStatus, settingsStatus, customFieldsStatus]),
{ status: projectDataStatus, isLoadingError: projectDataIsLoadingError },
{ status: viewSettingsStatus, isLoadingError: viewSettingsIsLoadingError },
{ status: settingsStatus, isLoadingError: settingsIsLoadingError },
{ status: customFieldsStatus, isLoadingError: customFieldsIsLoadingError },
]),
}; };
} }
@@ -16,18 +16,10 @@ export interface TimelineData {
export function useTimelineData(): ViewData<TimelineData> { export function useTimelineData(): ViewData<TimelineData> {
// HTTP API data // HTTP API data
const { const { data: rundownData, status: rundownStatus } = useFlatRundownWithMetadata();
data: rundownData, const { data: projectData, status: projectDataStatus } = useProjectData();
status: rundownStatus, const { data: settings, status: settingsStatus } = useSettings();
isLoadingError: rundownIsLoadingError, const { data: customFields, status: customFieldsStatus } = useCustomFields();
} = useFlatRundownWithMetadata();
const { data: projectData, status: projectDataStatus, isLoadingError: projectDataIsLoadingError } = useProjectData();
const { data: settings, status: settingsStatus, isLoadingError: settingsIsLoadingError } = useSettings();
const {
data: customFields,
status: customFieldsStatus,
isLoadingError: customFieldsIsLoadingError,
} = useCustomFields();
return { return {
data: { data: {
@@ -36,11 +28,6 @@ export function useTimelineData(): ViewData<TimelineData> {
projectData, projectData,
settings, settings,
}, },
status: aggregateQueryStatus([ status: aggregateQueryStatus([rundownStatus, projectDataStatus, settingsStatus, customFieldsStatus]),
{ status: rundownStatus, isLoadingError: rundownIsLoadingError },
{ status: projectDataStatus, isLoadingError: projectDataIsLoadingError },
{ status: settingsStatus, isLoadingError: settingsIsLoadingError },
{ status: customFieldsStatus, isLoadingError: customFieldsIsLoadingError },
]),
}; };
} }
+10 -18
View File
@@ -22,19 +22,11 @@ export function useTimerData(): ViewData<TimerData> {
const isMirrored = useViewOptionsStore((state) => state.mirror); const isMirrored = useViewOptionsStore((state) => state.mirror);
// HTTP API data // HTTP API data
const { data: projectData, status: projectDataStatus, isLoadingError: projectDataIsLoadingError } = useProjectData(); const { data: projectData, status: projectDataStatus } = useProjectData();
const { const { data: viewSettings, status: viewSettingsStatus } = useViewSettings();
data: viewSettings, const { data: settings, status: settingsStatus } = useSettings();
status: viewSettingsStatus, const { data: customFields, status: customFieldsStatus } = useCustomFields();
isLoadingError: viewSettingsIsLoadingError, const { data: rundown, status: rundownStatus } = useRundown();
} = useViewSettings();
const { data: settings, status: settingsStatus, isLoadingError: settingsIsLoadingError } = useSettings();
const {
data: customFields,
status: customFieldsStatus,
isLoadingError: customFieldsIsLoadingError,
} = useCustomFields();
const { data: rundown, status: rundownStatus, isLoadingError: rundownIsLoadingError } = useRundown();
const { entries } = rundown; const { entries } = rundown;
return { return {
@@ -47,11 +39,11 @@ export function useTimerData(): ViewData<TimerData> {
entries, entries,
}, },
status: aggregateQueryStatus([ status: aggregateQueryStatus([
{ status: projectDataStatus, isLoadingError: projectDataIsLoadingError }, projectDataStatus,
{ status: viewSettingsStatus, isLoadingError: viewSettingsIsLoadingError }, viewSettingsStatus,
{ status: settingsStatus, isLoadingError: settingsIsLoadingError }, settingsStatus,
{ status: customFieldsStatus, isLoadingError: customFieldsIsLoadingError }, customFieldsStatus,
{ status: rundownStatus, isLoadingError: rundownIsLoadingError }, rundownStatus,
]), ]),
}; };
} }
+11 -17
View File
@@ -5,27 +5,21 @@ export type ViewData<T> = {
status: QueryStatus; status: QueryStatus;
}; };
type AggregatableQuery = {
status: QueryStatus;
/** true only when the query has never received data, ie useQuery's isLoadingError */
isLoadingError: boolean;
};
/** /**
* Aggregates a loading status from multiple queries, for the purpose of deciding * Aggregates a loading status from multiple query statuses.
* whether a view can render. * If all statuses are 'pending', returns 'pending'.
* - 'pending' while any query hasn't settled yet (no result, first fetch in flight) * If all statuses are 'success', returns 'success'.
* - 'error' once all queries have settled, if any of them never received data * If any status is 'error', returns 'error'.
* - 'success' once all queries have settled and every one has data to show,
* even if a query's last (background) fetch failed
*/ */
export function aggregateQueryStatus(queries: AggregatableQuery[]): QueryStatus { export function aggregateQueryStatus(statuses: QueryStatus[]): QueryStatus {
const allSettled = queries.every((query) => query.status !== 'pending'); if (statuses.every((status) => status === 'pending')) {
if (!allSettled) {
return 'pending'; return 'pending';
} }
if (queries.some((query) => query.isLoadingError)) { if (statuses.every((status) => status === 'success')) {
return 'success';
}
if (statuses.some((status) => status === 'error')) {
return 'error'; return 'error';
} }
return 'success'; return 'pending';
} }
+40 -11
View File
@@ -137,8 +137,8 @@ importers:
specifier: 5.6.0 specifier: 5.6.0
version: 5.6.0(react@19.2.7) version: 5.6.0(react@19.2.7)
react-router: react-router:
specifier: ^8.0.1 specifier: ^8.3.0
version: 8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) version: 8.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
react-virtuoso: react-virtuoso:
specifier: ^4.18.7 specifier: ^4.18.7
version: 4.18.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7) version: 4.18.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
@@ -1508,6 +1508,9 @@ packages:
'@jridgewell/trace-mapping@0.3.30': '@jridgewell/trace-mapping@0.3.30':
resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==}
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@lezer/common@1.5.1': '@lezer/common@1.5.1':
resolution: {integrity: sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==} resolution: {integrity: sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==}
@@ -2972,8 +2975,8 @@ packages:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
acorn@8.16.0: acorn@8.18.0:
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
hasBin: true hasBin: true
@@ -3726,6 +3729,10 @@ packages:
resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==}
engines: {node: '>=14.14'} engines: {node: '>=14.14'}
fs-extra@11.4.0:
resolution: {integrity: sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==}
engines: {node: '>=14.14'}
fs-extra@7.0.1: fs-extra@7.0.1:
resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==}
engines: {node: '>=6 <7 || >=8'} engines: {node: '>=6 <7 || >=8'}
@@ -4049,6 +4056,9 @@ packages:
jsonfile@6.2.0: jsonfile@6.2.0:
resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
jsonfile@6.2.1:
resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==}
jwa@2.0.0: jwa@2.0.0:
resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==}
@@ -4595,8 +4605,8 @@ packages:
resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
react-router@8.0.1: react-router@8.3.0:
resolution: {integrity: sha512-5EL/fANovVUhRK50NLS8RYfX0BxrimoKsHWUPPy8v5UEl8i6vzF7e4POo3u+AhPItDwccUAJjMfIOmydxBJmQw==} resolution: {integrity: sha512-qyPMvW83jGIct3yiieisxdk9M745anqhpIMKN5m1t6yBMfgVPpt77aHOqs5fUlEJRMCGffg9BaQLH9oPVOL7xQ==}
engines: {node: '>=22.22.0'} engines: {node: '>=22.22.0'}
peerDependencies: peerDependencies:
react: '>=19.2.7' react: '>=19.2.7'
@@ -6303,7 +6313,7 @@ snapshots:
dependencies: dependencies:
cross-dirname: 0.1.0 cross-dirname: 0.1.0
debug: 4.4.3 debug: 4.4.3
fs-extra: 11.3.4 fs-extra: 11.4.0
minimist: 1.2.8 minimist: 1.2.8
postject: 1.0.0-alpha.6 postject: 1.0.0-alpha.6
transitivePeerDependencies: transitivePeerDependencies:
@@ -6627,7 +6637,7 @@ snapshots:
'@jridgewell/source-map@0.3.11': '@jridgewell/source-map@0.3.11':
dependencies: dependencies:
'@jridgewell/gen-mapping': 0.3.13 '@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.30 '@jridgewell/trace-mapping': 0.3.31
'@jridgewell/sourcemap-codec@1.5.5': {} '@jridgewell/sourcemap-codec@1.5.5': {}
@@ -6636,6 +6646,11 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/sourcemap-codec': 1.5.5
'@jridgewell/trace-mapping@0.3.31':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@lezer/common@1.5.1': {} '@lezer/common@1.5.1': {}
'@lezer/css@1.3.3': '@lezer/css@1.3.3':
@@ -7741,7 +7756,7 @@ snapshots:
mime-types: 3.0.1 mime-types: 3.0.1
negotiator: 1.0.0 negotiator: 1.0.0
acorn@8.16.0: {} acorn@8.18.0: {}
adler-32@1.3.1: {} adler-32@1.3.1: {}
@@ -8644,6 +8659,13 @@ snapshots:
jsonfile: 6.2.0 jsonfile: 6.2.0
universalify: 2.0.1 universalify: 2.0.1
fs-extra@11.4.0:
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.2.1
universalify: 2.0.1
optional: true
fs-extra@7.0.1: fs-extra@7.0.1:
dependencies: dependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@@ -9025,6 +9047,13 @@ snapshots:
optionalDependencies: optionalDependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
jsonfile@6.2.1:
dependencies:
universalify: 2.0.1
optionalDependencies:
graceful-fs: 4.2.11
optional: true
jwa@2.0.0: jwa@2.0.0:
dependencies: dependencies:
buffer-equal-constant-time: 1.0.1 buffer-equal-constant-time: 1.0.1
@@ -9512,7 +9541,7 @@ snapshots:
react-refresh@0.18.0: {} react-refresh@0.18.0: {}
react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7): react-router@8.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
dependencies: dependencies:
cookie-es: 3.1.1 cookie-es: 3.1.1
react: 19.2.7 react: 19.2.7
@@ -9928,7 +9957,7 @@ snapshots:
terser@5.46.2: terser@5.46.2:
dependencies: dependencies:
'@jridgewell/source-map': 0.3.11 '@jridgewell/source-map': 0.3.11
acorn: 8.16.0 acorn: 8.18.0
commander: 2.20.3 commander: 2.20.3
source-map-support: 0.5.21 source-map-support: 0.5.21