Hot Reloading Salesforce Records with Change Data Capture and Lightning Web Components
Have you ever been working in Salesforce, only to realize that the record you’re looking at has been updated by someone else, but you had no idea until you refreshed the page? Frustrating, right? Wouldn’t it be great if records could just update themselves in real-time, without any manual refreshes? Well, good news — they can! In this post, we’ll explore how to use Salesforce’s Change Data Capture events and Lightning Web Components (LWCs) to create a hot-reloading experience for your records.
Why Hot Reloading Matters
In a dynamic business environment, data changes rapidly. Sales reps update opportunities, support agents close cases, and records evolve continuously. Relying on manual page refreshes to see the latest data isn’t just inconvenient; it can lead to outdated information and misinformed decisions. By implementing real-time data updates, users can stay in sync effortlessly.
Understanding Change Data Capture
Change Data Capture (CDC) is a Salesforce feature that publishes change events, which represent changes to Salesforce records. These events are broadcasted over a messaging channel that subscribers can listen to. When a record is created, updated, deleted, or undeleted, a change event is published. This makes CDC perfect for keeping components in sync with the latest data.
Benefits of Using Change Data Capture
- Real-Time Updates: Receive notifications instantly when data changes.
- Efficiency: Reduce the need for manual refreshes or polling for changes.
- Scalability: Handle a high volume of changes without performance hits.
Setting Up Change Data Capture for Your Object
Before we dive into the code, let’s set up CDC for the object you want to monitor.
Step 1: Enable Change Data Capture
- Navigate to Setup: In Salesforce, click the gear icon and select Setup.
- Find Change Data Capture: In the Quick Find box, type “Change Data Capture”.
- Select Objects: Check the boxes next to the objects you want to monitor. For example, if you’re interested in the Account object, select it.
- Save: Click Save to enable CDC for those objects.
That’s it! You’ve now set up a messaging channel for your object. Salesforce will start publishing change events for it.
Object Limitations and Considerations
Not all Salesforce objects support Change Data Capture. It’s important to know which objects are eligible so you don’t run into unexpected issues.
Finding Supported Objects
To find out which objects support Change Data Capture:
- Salesforce Documentation: Refer to the Change Data Capture Supported Objects in the Salesforce Developer Guide.
- In Setup: When you navigate to Setup > Change Data Capture, Salesforce displays a list of available objects for CDC. If an object isn’t listed, it’s not supported.
Examples of Supported Objects
- Standard Objects:
- Account
- Contact
- Opportunity
- Case
- Lead
- Custom Objects:
- Any custom object you have created in your org (e.g., Invoice__c, Project__c)
Examples of Unsupported Objects
- Activities:
- Task
- Event
- Setup and Configuration Objects:
- User
- Profile
- PermissionSet
- Some Standard Objects:
- Attachment
- ContentDocument
Considerations
- Limited Support for Certain Objects: Some objects may partially support CDC, meaning only certain fields or events trigger change events.
- High-Volume Objects: For objects with a high volume of changes, consider the potential performance impact and governor limits.
Building the Lightning Web Component
Now, let’s create an LWC that listens to these change events and updates the record in real-time.
Component Overview
Our component, PageListener, will:
- Subscribe to the change event channel for the specified object.
- Listen for events related to the current record.
- Refresh the record view when a change is detected.
The Code Breakdown
Below is the complete code for the PageListener component.
JavaScript Controller: pageListener.js
import { api, LightningElement, wire, track } from 'lwc'; import { getRecord, getRecordNotifyChange } from 'lightning/uiRecordApi'; import { subscribe, unsubscribe, onError } from 'lightning/empApi'; import { ShowToastEvent } from 'lightning/platformShowToastEvent'; export default class PageListener extends LightningElement { @api recordId; @api objectApiName; @wire(getRecord, { recordId: '$recordId', layoutTypes: 'Full' }) record; subscription = {}; // holds subscription, used for unsubscribe connectedCallback() { console.log('Connecting to :', this.objectApiName + 'ChangeEvent'); console.log('Connected to change event: ', this.channelName); this.registerErrorListener(); this.registerSubscribe(); } disconnectedCallback() { unsubscribe(this.subscription, () => console.log('Unsubscribed to change events.')); } // Called by connectedCallback() registerErrorListener() { onError(error => { console.error('Salesforce error', JSON.stringify(error)); }); } // Called by connectedCallback() registerSubscribe() { const changeEventCallback = changeEvent => { this.processChangeEvent(changeEvent); }; // Sets up subscription and callback for change events subscribe('/data/' + this.objectApiName + 'ChangeEvent', -1, changeEventCallback).then(subscription => { this.subscription = subscription; }); } // Called by registerSubscribe() processChangeEvent(changeEvent) { try { const recordIds = changeEvent.data.payload.ChangeEventHeader.recordIds; // Avoid destructuring if (recordIds.includes(this.recordId)) { // Refresh all components related to this record getRecordNotifyChange([{ recordId: this.recordId }]); } } catch (err) { console.error(err); // Optionally, dispatch an error toast this.dispatchEvent( new ShowToastEvent({ title: 'Error', message: 'An error occurred while processing the record update.', variant: 'error', mode: 'dismissable' }) ); } } }
HTML Template: pageListener.html
<template> <!-- This component doesn't render anything --> </template>
Meta Configuration: pageListener.js-meta.xml
<?xml version="1.0" encoding="UTF-8" ?> <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> <apiVersion>61.0</apiVersion> <isExposed>true</isExposed> <masterLabel>Page Listener</masterLabel> <description>Listens to changes for a record using Change Data Capture</description> <targets> <target>lightning__RecordPage</target> </targets> <targetConfigs> <targetConfig targets="lightning__RecordPage"> <property name="objectApiName" type="String" label="Object API Name" description="API name of the object to listen for changes" /> </targetConfig> </targetConfigs> </LightningComponentBundle>
Key Parts Explained
- @api recordId and @api objectApiName: These properties allow the component to be aware of the current record and object.
- connectedCallback and disconnectedCallback: Lifecycle hooks used to subscribe and unsubscribe from the change event channel.
- subscribe and unsubscribe: Methods from lightning/empApi used to handle event subscriptions.
- proccessChangeEvent: The function that processes incoming change events and refreshes the record if necessary.
- getRecordNotifyChange: This method notifies all Lightning Data Service components to refresh their cache for the specified record.
How Real-Time Updates Work
Let’s walk through how the component ensures that users see the latest data without interfering with their current actions.
Subscribing to Change Events
When the component is initialized (connectedCallback), it subscribes to the /data/ObjectNameChangeEvent channel. This channel broadcasts any changes to records of that object type.
Processing Change Events
When a change event occurs, the handleChangeEvent method checks if the changed record's ID matches the one currently being viewed. If it does, it calls getRecordNotifyChange, which tells all components using Lightning Data Service to refresh their data for that record.
Safe Updates During Edits
One of the concerns with real-time updates is what happens if a user is in the middle of editing a record. The good news is that getRecordNotifyChange is smart. It refreshes the data cache but doesn't overwrite unsaved changes in an edit form. This means:
- Viewing User: Sees the updated data almost instantly.
- Editing User: Continues editing without disruption. Once they save, they’ll be working with the latest data.
Ensuring Data Consistency
By using getRecordNotifyChange, we ensure that all components tied to the record are updated. This includes:
- Record details
- Related lists
- Custom components using @wire(getRecord)
Adding the Component to a Record Page
To make this work, you’ll need to add the PageListener component to your record page.
- Navigate to a Record Page: Go to any record of the object you set up.
- Edit Page: Click the gear icon and select Edit Page.
- Drag and Drop the Component: Find your PageListener component in the list and drag it onto the page. Since it doesn't render anything, placement isn't critical.
- Set the Object API Name: In the component’s properties pane, set the Object API Name to match your object (e.g., Account).
- Save and Activate: Save your changes and activate the page if necessary.
Now, your component is live and listening for changes!
Testing the Real-Time Updates
Let’s make sure everything works as expected.
- Open Two Browser Windows: Log into Salesforce in two separate windows or browsers.
- View the Same Record: In both windows, navigate to the same record.
- Edit and Save in One Window: Make a change to the record in one window and save it.
- Watch the Other Window: In the other window, observe that the record details update automatically without a refresh.
Wrapping Up
By leveraging Change Data Capture and Lightning Web Components, we’ve created a seamless, real-time experience for Salesforce users. No more manual refreshes or outdated information — just instant updates as they happen.
Discussions
Login to Post Comments