fix: guard NavigateTo*RecordSingleRecordCommand against missing record selection
https://sonarly.com/issue/7596?type=bug The `NavigateToPreviousRecordSingleRecordCommand` and `NavigateToNextRecordSingleRecordCommand` components throw "Selected record ID is required" when rendered on an index page (or during a show-to-index page transition) because the server-driven command menu path lacks the `availableOn` view-type guard present in the legacy path. Fix: Created a new `useSelectedRecordIdOrNull` hook that returns `null` instead of throwing when no record is selected. Updated `NavigateToPreviousRecordSingleRecordCommand` and `NavigateToNextRecordSingleRecordCommand` to use this non-throwing variant. Both components now return `null` (render nothing) when there's no selected record, preventing the "Selected record ID is required" error during page transitions. The root cause is that the server-driven command menu path (`IS_COMMAND_MENU_ITEM_ENABLED` feature flag) can render these components during show-to-index page navigation transitions before the context store atoms are fully updated via `useEffect`. The legacy path had synchronous `availableOn: [SHOW_PAGE]` filtering that prevented this. Rather than restructuring the server-driven path's filtering architecture, this fix makes the two affected components resilient to the transient state. **Files changed:** 1. `useSelectedRecordIdOrNull.tsx` (new) — Non-throwing variant of `useSelectedRecordIdOrThrow`, returns `null` when no record is selected 2. `NavigateToPreviousRecordSingleRecordCommand.tsx` — Uses `useSelectedRecordIdOrNull`, returns `null` when no record 3. `NavigateToNextRecordSingleRecordCommand.tsx` — Same change as above
This commit is contained in:
+7
-3
@@ -1,17 +1,21 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useSelectedRecordIdOrNull } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrNull';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination';
|
||||
|
||||
export const NavigateToNextRecordSingleRecordCommand = () => {
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const recordId = useSelectedRecordIdOrNull();
|
||||
|
||||
const { navigateToNextRecord } = useRecordShowPagePagination(
|
||||
objectMetadataItem.nameSingular,
|
||||
recordId,
|
||||
recordId ?? '',
|
||||
);
|
||||
|
||||
if (!recordId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Command onClick={navigateToNextRecord} />;
|
||||
};
|
||||
|
||||
+7
-3
@@ -1,17 +1,21 @@
|
||||
import { Command } from '@/command-menu-item/display/components/Command';
|
||||
import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useSelectedRecordIdOrNull } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrNull';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination';
|
||||
|
||||
export const NavigateToPreviousRecordSingleRecordCommand = () => {
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
const recordId = useSelectedRecordIdOrNull();
|
||||
|
||||
const { navigateToPreviousRecord } = useRecordShowPagePagination(
|
||||
objectMetadataItem.nameSingular,
|
||||
recordId,
|
||||
recordId ?? '',
|
||||
);
|
||||
|
||||
if (!recordId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Command onClick={navigateToPreviousRecord} />;
|
||||
};
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
|
||||
export const useSelectedRecordIdOrNull = (): string | null => {
|
||||
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
if (
|
||||
contextStoreTargetedRecordsRule.mode === 'exclusion' ||
|
||||
(contextStoreTargetedRecordsRule.mode === 'selection' &&
|
||||
contextStoreTargetedRecordsRule.selectedRecordIds.length === 0)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return contextStoreTargetedRecordsRule.selectedRecordIds[0];
|
||||
};
|
||||
Reference in New Issue
Block a user