[SalesForce] Lightning recordForm displaying stale data

After two days I'm out of ideas and hoping someone here could offer suggestions or even better — tell me where I'm being silly!

I am trying to refresh the view of the child (recordForm) but it is not refreshing, it is displaying cached data, not SF database data. Even after destroying and re-creating it. Any ideas how to refresh it?

In cases where the external server directly changes SF data (via API) the Child needs to refresh its view and so it emits an event to the parent who then destroys and re-creates the child. BUT the child data shown is still the OLD data, not the new data that I can clearly see in SF database.

I display the GUID on the child and it seems to hardly change, I expected it would always be unique.

I have disabled cache in settings, re-logged in, tried different browsers. I can only think I'm not really destroying the child properly?

PARENT – COMPONENT

<!-- AccountManager.cmp -->
<aura:component controller='CRAccountGetService' implements='flexipage:availableForRecordHome,force:hasRecordId,force:hasSobjectName' access='global'>

<!-- Attributes-->
<aura:attribute name='recordId' type='string' default='{! v.recordId }' access='global' />
<aura:attribute name='sObjectName' type='string' access='global' />

<!-- Handlers -->
<aura:handler name='init' value='{! this }' action='{! c.handleInit }' />
<aura:handler name="refreshAccountForm" event="c:CRRefreshAccountForm" action='{! c.handleRefreshAccountForm }' />

   <!-- Screen -->
    <div>
       <!-- AccountForm dynamically injected here  -->
       {! v.body }
   </div>
</aura:component>

PARENT – CONTROLLER

 // AccountManagerController.js
  ({
    handleInit: function(component, event, helper) {
        helper.newAccountForm(component);
    },

   // handler for child component refresh event. Destroy & recreate account form component
    handleRefreshAccountForm: function(component, event, helper) {

    // destroy existing Account form child component
    var body = component.get("v.body");
    var arrayLength = body.length;
    for (var i = 0; i < arrayLength; i++) {
        body[i].destroy();
    }
    component.set('v.body',[]);

    // create new Account form child component
    var recId = component.get('v.recordId');
    $A.createComponent("c:AccountForm", { 'recordId': recId }, 
                        function (newAccountForm, status, errorMsg) {
                            if (status === "SUCCESS") {
                                component.set("v.body", newAccountForm);
                            } else {
                               // show error
                            }
                        });
    },
})

CHILD – COMPONENT

    <aura:handler name='init' value='{! this }' action='{! c.handleInit }' />

 <!-- Event to let parent (manager) know we need to be refreshed -->
    <aura:registerEvent name="refreshAccountForm" type="c:CRRefreshAccountForm" />
    <aura:attribute name='recordId' type='string' default='{! v.recordId }' access='global' />
    <aura:attribute name='mode' type='string' default='view' access='global' />

     <!-- Screen -->
     <lightning:messages />

    <lightning:recordForm 
        aura:id='accountForm' 
        objectApiName='Account'
        recordId='{! v.recordId }'
        layoutType='Full'
        columns='2'
        mode='{! v.mode }'
        onsuccess='{! c.handleSuccess }' 
    />
</aura:component>

CHILD CONTROLLER

 // AccountFormController.js
 ({
     // After a successful save to SF database, call external service
     handleSuccess: function(component, event, helper) {
         helper.callExternalService(component, event, helper).then (
            // promise resolved: external server accepted data. SF database unchanged
            $A.getCallback(function () {
                 helper.showToast('Account Save', 'All good', 'success', 1000);
             }),

             // promise rejected: external server rejected data and overwrote values in SF database, 
             // fire event so the Manager (the parent component) refreshes the form view
             $A.getCallback(function (message) { 
                 var refreshMeEvent = component.getEvent("refreshAccountForm");
                 refreshMeEvent.setParam("message", message);
                 refreshMeEvent.fire();
             })
         );
     },
 })

CHILD – HELPER

  // AccountFormHelper.js
  ({
      callExternalService: function(component,event, helper) {
         return new Promise($A.getCallback(function(resolve, reject) {

           var externalService = component.get('c.callExternalService');
               externalService.setParams({
                   'accountId': component.get('v.recordId')
              });

              externalService.setCallback(this, function(response) {
                  if (response.getState() === "SUCCESS") {
                       var rspMsg = JSON.parse(response.getReturnValue());
                       if (rspMsg.success) {
                           resolve();
                       } else {
                          reject(rspMsg.message);
                      }
                   } else {
                      reject('Controller failure:' + response.getState()); 
                  }
              });
               $A.enqueueAction(externalService);
          }));
      },
 })

Best Answer

I don't have documented proof of this, but I've also explored something similar to this and found these results, hopefully they point you in the right direction:

Per documentation, lightning:recordForm, lightning:recordEditForm, and lightning:recordViewForm all wrap force:recordData or some variant, which itself uses LDS.

However, it seems at the upper most layer, the lightning: namespace components do not expose the utility methods force:recordData have, such as:

component.find("forceRecordAuraId").reloadRecord({ skipCache: true });

My best guess on your current situation is that while your component is destroyed, LDS is actually not bound to the component, rather, the LEX container itself. So, the instance of LDS created by lightning: forms cannot be accessed by the component creating it.

As of Summer 18 (v43), I don't believe there is any way override the global LDS cache without going through one of the documented utility methods (i.e. force:recordData). If someone can show otherwise, I would also love to learn as I got stuck with something like this as well.