I'm using Salesforce Mobile SDK 12.2.0 in a React Native app and trying to sync up records using an External ID (Delivery_Id__c) to prevent duplicates. However, I'm still seeing duplicate records in Salesforce when syncing up.
This seems to be happening when the initial sync-up fails due to network issues. The record is sent to Salesforce, but the sync-up returns an error, and SmartStore is not updated. Mobile SDK retries the sync when the device regains connectivity, leading to duplicates.
What I Have Done- Configured SyncUp to Use External ID
await syncUp( false, // User store { androidImpl: "com.salesforce.androidsdk.mobilesync.target.ParentChildrenSyncUpTarget", createFieldlist: ["Delivery_Id__c", "Start__c", "End__c", "Driver__c", "Status__c", "Signature__c", "Signer_Name__c", "Account__c", "Vehicle__c"], updateFieldlist: ["Start__c", "Driver__c", "Status__c", "Signature__c", "Signer_Name__c", "Account__c", "Vehicle__c"], parent: { idFieldName: "Id", sobjectType: "Delivery__c", modificationDateFieldName: "LastModifiedDate", externalIdFieldName: "Delivery_Id__c", // External ID soupName: "Delivery__c", }, children: { parentIdFieldName: "Delivery__c", idFieldName: "Id", sobjectType: "Delivery_Item__c", modificationDateFieldName: "LastModifiedDate", externalIdFieldName: "Item_Id__c", soupName: "Delivery_Item__c", sobjectTypePlural: "Delivery_items__r", }, relationshipType: "MASTER_DETAIL", }, "Delivery__c", { mergeMode: mobilesync.MERGE_MODE.LEAVE_IF_CHANGED } ); Error Logs (External ID is set to unique)
{ "message": "duplicate value found: Delivery_Id__c duplicates value on record with id: a0tQy000008BZp4IAG", "errorCode": "DUPLICATE_VALUE" } This indicates that Salesforce is treating the sync as a create instead of an upsert, even though externalIdFieldName is set. What I Expected - Upsert behavior should find the record by Delivery_Id__c and update it, not create a duplicate.
After sync, local records should be updated with the Salesforce ID.
What’s Actually Happening Some records are created as duplicates instead of being updated.
Questions Am I missing anything in my sync configuration? Why does Mobile SDK still create duplicates even with externalIdFieldName set? Is this a known issue with Salesforce Mobile SDK 13.0.0? How do I ensure that SmartStore correctly updates the record ID after a sync-up?
Here is my smartstore setup
// Fields for Delivery soup const deliveryFields = [ { path: "Id", type: "string" }, { path: "Delivery_Id__c", type: "string" }, { path: "Start__c", type: "string" }, { path: "Driver__c", type: "string" }, { path: "Status__c", type: "string" }, { path: "Signature__c", type: "string" }, { path: "Signer_Name__c", type: "string" }, { path: "Account__c", type: "string" }, { path: "Account_Name__c", type: "string" }, { path: "BillingStreet", type: "string" }, { path: "Billing_City__c", type: "string" }, { path: "Billing_Postalcode__c", type: "string" }, { path: "Vehicle__c", type: "string" }, { path: "Shift__c", type: "string" }, { path: "__local__", type: "string" }, { path: "__locally_created__", type: "string" }, { path: "__locally_updated__", type: "string" }, { path: "__locally_deleted__", type: "string" }, { path: "_soupEntryId", type: "string" }, { path: "__sync_id__", type: "string" }, { path: "__last_error__", type: "string" }, ]; // Fields for Delivery Item soup const deliveryItemFields = [ { path: "Id", type: "string" }, { path: "Item_Id__c", type: "string" }, { path: "Delivery__c", type: "string" }, { path: "Product__c", type: "string" }, { path: "Quantity__c", type: "integer" }, { path: "__local__", type: "string" }, { path: "__locally_created__", type: "string" }, { path: "__locally_updated__", type: "string" }, { path: "__locally_deleted__", type: "string" }, { path: "_soupEntryId", type: "string" }, { path: "__sync_id__", type: "string" }, { path: "__last_error__", type: "string" }, ]; And here is what I add to smartstore
const deliveryData = { Delivery_Id__c: `local_${Date.now()}`, // Temporary ID for SmartStore Id: `Id${Date.now()}`, // Temporary ID for SmartStore Status__c: status, Account__c: selectedAccount.Id, Driver__c: driver.id, Start__c: deliveryDate.toISOString(), End__c: endDate.toISOString(), Account_Name__c: selectedAccount.Name, BillingStreet: selectedAccount.BillingStreet, Billing_City__c: selectedAccount.BillingCity, Billing_Postalcode__c: selectedAccount.BillingPostalCode, __local__: 'true', __locally_created__: 'true', __locally_updated__: 'false', __locally_deleted__: 'false', attributes: { type: "Delivery__c" }, }; // Include signature and signer name if applicable if (status === 'Complete') { deliveryData.Signature__c = `<img src="${signature}" alt="Signature" />`; deliveryData.Signer_Name__c = name.trim(); } console.log('Delivery Payload:', deliveryData); // ✅ 🚀 Close page immediately before saving navigation.goBack(); // Save the delivery to SmartStore smartstore.upsertSoupEntries( false, // Don't use global store 'Delivery__c', // Soup name [deliveryData], // Entries to upsert (result) => { console.log('Successfully saved delivery to SmartStore:', result); // Save delivery items to SmartStore const deliveryItemsPayload = deliveryItems.map((item, index) => ({ Item_Id__c: `local_item_${Date.now()}_${index}`, //Id: `local_item_${Date.now()}_${index}`, Delivery__c: deliveryData.Delivery_Id__c, Product__c: item.product, Quantity__c: parseFloat(item.quantity), __local__: 'true', __locally_created__: 'true', __locally_updated__: 'false', __locally_deleted__: 'false', attributes: { type: "Delivery_Item__c" }, })); smartstore.upsertSoupEntries( false, 'Delivery_Item__c', deliveryItemsPayload, (itemResult) => { markDeliveryUnsynced(); console.log('Successfully saved delivery items to SmartStore:', itemResult); DeliveryEventEmitter.emit('REFRESH_DELIVERIES', { refresh: true }); }, (error) => { console.error('Error saving delivery items to SmartStore:', error); } ); }, (error) => { console.error('Error saving delivery to SmartStore:', error); } );