From e6b7f31feadc9dd25db8e3b2dc139efba953bf02 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Tue, 19 May 2026 00:40:37 +0200 Subject: [PATCH] fix(front): prevent standalone page layout crash from useTargetRecord (#20698) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context Reported in production on `engineering.twenty.com` — standalone page layouts (e.g. "Release overview") crash with the React error boundary fallback ("Sorry, something went wrong"). The console shows: ``` Error: useTargetRecord must be used within a record page context (targetRecordIdentifier is required) ``` The minified stack trace points at `SidePanelToggleButto…`, but that's just the bundle chunk name — the actual call site is `PageLayoutTabsRenderer`. ## Root cause #19296 added an unconditional `useTargetRecord()` call inside `PageLayoutTabsRenderer` so it could read the target object's metadata and hide tabs whose widgets reference deactivated relations: ```ts const targetRecord = useTargetRecord(); const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular: targetRecord.targetObjectNameSingular, }); ``` But `PageLayoutTabsRenderer` runs on **both** record pages and standalone pages. On standalone pages, `StandalonePageLayoutPage` intentionally sets `targetRecordIdentifier: undefined` in `LayoutRenderingProvider`, which makes `useTargetRecord()` throw — and the follow-up `useObjectMetadataItem()` would also throw on miss. --- .../components/PageLayoutTabsRenderer.tsx | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabsRenderer.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabsRenderer.tsx index ea84859c64..c6c475f1b6 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabsRenderer.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabsRenderer.tsx @@ -1,6 +1,6 @@ import { metadataStoreState } from '@/metadata-store/states/metadataStoreState'; import { type FlatObjectMetadataItem } from '@/metadata-store/types/FlatObjectMetadataItem'; -import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { PageLayoutLeftPanel } from '@/page-layout/components/PageLayoutLeftPanel'; import { PageLayoutTabList } from '@/page-layout/components/PageLayoutTabList'; import { PageLayoutTabListEffect } from '@/page-layout/components/PageLayoutTabListEffect'; @@ -19,7 +19,6 @@ import { getTabsWithVisibleWidgets } from '@/page-layout/utils/getTabsWithVisibl import { shouldEnableTabEditingFeatures } from '@/page-layout/utils/shouldEnableTabEditingFeatures'; import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition'; import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; -import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; @@ -75,16 +74,22 @@ export const PageLayoutTabsRenderer = () => { currentPageLayout.id, ); - const targetRecord = useTargetRecord(); - - const { objectMetadataItem } = useObjectMetadataItem({ - objectNameSingular: targetRecord.targetObjectNameSingular, - }); + const { objectMetadataItems } = useObjectMetadataItems(); const inactiveRelationFieldNames = useMemo(() => { + if (!isDefined(targetRecordIdentifier)) { + return new Set(); + } + + const objectMetadataItem = objectMetadataItems.find( + (item) => + item.nameSingular === targetRecordIdentifier.targetObjectNameSingular, + ); + if (!isDefined(objectMetadataItem)) { return new Set(); } + return new Set( objectMetadataItem.fields .filter( @@ -95,7 +100,7 @@ export const PageLayoutTabsRenderer = () => { ) .map((field) => field.name), ); - }, [objectMetadataItem]); + }, [objectMetadataItems, targetRecordIdentifier]); const isMobile = useIsMobile();