Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 5f316902ea fix: add array guard in getActivityTargetObjectRecords to prevent crash on non-array field values
https://sonarly.com/issue/15203?type=bug

When rendering relation-from-many fields on the People list view, `useActivityTargetObjectRecords` is called unconditionally with `fieldValue` before validating it's an array, causing `TypeError: a.map is not a function` when the value is truthy but not an array.

Fix: Added an `Array.isArray` guard on the `targets` variable in `getActivityTargetObjectRecords.ts` before the `.map()` call on line 75.

**What changed:** Renamed `targets` to `rawTargets` for the initial derivation, then added a guard that returns `[]` if `rawTargets` is not an array. The validated value is then assigned back to `targets` for the rest of the function.

**Why this fix:** The `RelationFromManyFieldDisplay` component calls `useActivityTargetObjectRecords` unconditionally for ALL relation-from-many fields (not just activity target fields), passing `fieldValue` with an unsafe type cast (`fieldValue as NoteTarget[] | TaskTarget[]`). When `fieldValue` is truthy but not an array (e.g., after a failed/partial GraphQL fetch or a newly created field with unexpected data shape), the `activityTargets` parameter passes the truthiness check on line 32 and flows directly into `.map()`, causing `TypeError: a.map is not a function`.

The `Array.isArray` guard is placed in `getActivityTargetObjectRecords` rather than the component because:
1. It's the function that makes the assumption about the data being an array
2. The component's existing `isArray(fieldValue)` check on line 70 runs AFTER the hook has already executed and crashed
3. Hooks cannot be called conditionally in React, so the component can't skip the hook call
2026-03-16 13:43:50 +00:00
@@ -29,7 +29,7 @@ export const getActivityTargetObjectRecords = ({
const isNote = isDefined(activityRecord) && 'noteTargets' in activityRecord;
const targets = activityTargets
const rawTargets = activityTargets
? activityTargets
: 'noteTargets' in activityRecord && isDefined(activityRecord.noteTargets)
? activityRecord.noteTargets
@@ -37,6 +37,12 @@ export const getActivityTargetObjectRecords = ({
? activityRecord.taskTargets
: [];
if (!Array.isArray(rawTargets)) {
return [];
}
const targets = rawTargets;
const activityTargetObjectNameSingular = isNote
? CoreObjectNameSingular.NoteTarget
: CoreObjectNameSingular.TaskTarget;