Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 89516f06bb 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
2026-03-16 09:36:46 +00:00
3 changed files with 32 additions and 6 deletions
@@ -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} />;
};
@@ -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} />;
};
@@ -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];
};