Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 5c16d2e79e fix: guard against undefined fieldMetadataItem in RecordTableCellFieldContextWrapper
https://sonarly.com/issue/19112?type=bug

RecordTableCellFieldContextWrapper crashes with TypeError when a record field references a fieldMetadataItemId that doesn't exist in the field metadata map, causing the workflows table view to fail to render.

Fix: Added `isDefined` null guards in three record table cell components that look up field metadata/definitions from maps by `fieldMetadataItemId` without checking for `undefined`:

1. **`RecordTableCellFieldContextWrapper.tsx`** — guards `fieldMetadataItem` before accessing `.name` (the exact crash site from the Sentry error)
2. **`RecordTableCellFieldContextGeneric.tsx`** — guards `fieldDefinition` before accessing `.metadata`, `.fieldMetadataId`, etc.
3. **`RecordTableCellFieldContextLabelIdentifier.tsx`** — guards `fieldDefinition` before accessing `.metadata.isUIReadOnly`, etc.

All three use early `return null` when the lookup returns `undefined`, which safely skips rendering the cell during transient metadata synchronization states. This matches the existing defensive pattern in `useRelevantRecordsGqlFields.ts` which uses `.filter(isDefined)` after the same kind of map lookup.

The `isDefined` utility from `twenty-shared/utils` is the team's standard null check (already imported in `RecordTableCellFieldContextGeneric.tsx`; added as new import to the other two files).
2026-03-27 18:07:57 +00:00
3 changed files with 14 additions and 0 deletions
@@ -44,6 +44,10 @@ export const RecordTableCellFieldContextGeneric = ({
const updateRecord = useContext(RecordTableUpdateContext);
if (!isDefined(fieldDefinition)) {
return null;
}
let hasObjectReadPermissions = objectPermissions.canReadObjectRecords;
// todo @guillim : adjust this to handle morph relations permissions display
@@ -10,6 +10,7 @@ import { RecordTableUpdateContext } from '@/object-record/record-table/contexts/
import { isRecordTableCellsNonEditableComponentState } from '@/object-record/record-table/states/isRecordTableCellsNonEditableComponentState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useContext, type ReactNode } from 'react';
import { isDefined } from 'twenty-shared/utils';
type RecordTableCellFieldContextLabelIdentifierProps = {
children: ReactNode;
@@ -49,6 +50,10 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
const fieldDefinition =
fieldDefinitionByFieldMetadataItemId[recordField.fieldMetadataItemId];
if (!isDefined(fieldDefinition)) {
return null;
}
const handleChipClick = () => {
onRecordIdentifierClick?.(rowIndex, recordId);
};
@@ -7,6 +7,7 @@ import { RecordTableCellFieldContextGeneric } from '@/object-record/record-table
import { RecordTableCellFieldContextLabelIdentifier } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextLabelIdentifier';
import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId';
import { type ReactNode } from 'react';
import { isDefined } from 'twenty-shared/utils';
type RecordTableCellFieldContextWrapperProps = {
children: ReactNode;
@@ -27,6 +28,10 @@ export const RecordTableCellFieldContextWrapper = ({
const fieldMetadataItem =
fieldMetadataItemByFieldMetadataItemId[recordField.fieldMetadataItemId];
if (!isDefined(fieldMetadataItem)) {
return null;
}
const instanceId = getRecordFieldInputInstanceId({
recordId,
fieldName: fieldMetadataItem.name,