Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 8487ae3fd3 Cache miss when attaching record in one-to-many relation picker on custom objects
https://sonarly.com/issue/4153?type=bug

When a user selects a record in the multi-select relation picker for a one-to-many field, the code attempts to read the target record from Apollo cache but fails because the record was fetched via search API and hasn't been fully cached yet.

Fix: Replaced the hard `throw new Error('Could not find cached related record')` with a graceful `isDefined(cachedTargetRecord)` guard in `useRecordOneToManyFieldAttachTargetRecord.ts`.

**What changed:** When `getRecordFromCache()` returns `null` (because the target record hasn't been hydrated into Apollo cache yet — common when records are found via search API), the code now skips the optimistic cache update and proceeds directly to the `updateOneRecord` API call. Previously it threw, which crashed the entire attach flow.

**Why this is correct:** The cache update block (lines 62-98) is an optimistic UI optimization that updates the local Apollo cache to reflect the previous relation being cleared. It is not required for correctness — the `updateOneRecord` mutation (line 100) is what actually persists the relation change on the server. When the mutation response returns, Apollo will update the cache anyway.

Also cleaned up the blank line in imports that was left by the original code.

Note: Two unmerged branches already contain essentially identical fixes (`311ac35ddf` by neo773, `467621ba21` by Thomas Trompette), confirming this is a known issue with a consensus fix approach.
2026-03-11 16:33:26 +00:00
@@ -1,7 +1,6 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { generateDepthRecordGqlFieldsFromRecord } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromRecord';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
@@ -60,44 +59,42 @@ export const useRecordOneToManyFieldAttachTargetRecord = () => {
objectPermissionsByObjectMetadataId,
});
if (!cachedTargetRecord) {
throw new Error('Could not find cached related record');
}
if (isDefined(cachedTargetRecord)) {
const previousRecordId = cachedTargetRecord[`${targetGQLFieldName}Id`];
const previousRecordId = cachedTargetRecord?.[`${targetGQLFieldName}Id`];
if (isDefined(previousRecordId)) {
const previousRecord = getRecordFromCache({
objectMetadataItem: sourceObjectMetadataItem,
recordId: previousRecordId,
cache: apolloCoreClient.cache,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
});
if (isDefined(previousRecordId)) {
const previousRecord = getRecordFromCache({
objectMetadataItem: sourceObjectMetadataItem,
recordId: previousRecordId,
cache: apolloCoreClient.cache,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
});
const previousRecordWithRelation = {
...cachedTargetRecord,
[targetGQLFieldName]: previousRecord,
};
const gqlFields = generateDepthRecordGqlFieldsFromRecord({
objectMetadataItem: targetObjectMetadataItem,
objectMetadataItems,
record: previousRecordWithRelation,
depth: 1,
});
updateRecordFromCache({
objectMetadataItems,
objectMetadataItem: targetObjectMetadataItem,
cache: apolloCoreClient.cache,
record: {
const previousRecordWithRelation = {
...cachedTargetRecord,
[targetGQLFieldName]: previousRecord,
},
recordGqlFields: gqlFields,
objectPermissionsByObjectMetadataId,
});
};
const gqlFields = generateDepthRecordGqlFieldsFromRecord({
objectMetadataItem: targetObjectMetadataItem,
objectMetadataItems,
record: previousRecordWithRelation,
depth: 1,
});
updateRecordFromCache({
objectMetadataItems,
objectMetadataItem: targetObjectMetadataItem,
cache: apolloCoreClient.cache,
record: {
...cachedTargetRecord,
[targetGQLFieldName]: previousRecord,
},
recordGqlFields: gqlFields,
objectPermissionsByObjectMetadataId,
});
}
}
await updateOneRecord({