In this article, I revisit the Map component I shared a few months ago, incorporating some lessons learned around component lifecycle, timing, and race conditions.

Check out this video to see the new Map component in action in the Lightning Experience. The component shows all the locations for a specific account.

https://youtu.be/p5wUdosy70o

Basic Component Setup

Code Highlights:

  • The component uses ltng:require to load the Leaflet Javascript library and CSS style sheet. The jsLoaded event handler is called after the files are loaded.
  • The accounts attribute holds a list of accounts to display on the map. The accounts are retrieved in the parent component and passed to the map component as an attribute.
  • The accountsChangeHandler event handler is called when the value of the accounts attribute changes.

Avoiding Race Conditions

From wikipedia: “A race condition is the behavior of a software system where the output is dependent on the sequence or timing of other uncontrollable events. It becomes a bug when events do not happen in the order the programmer intended”.

To display the map with a marker for each account in the list, the component relies on different operations happening in parallel:

  1. The rendering of the component
  2. The asynchronous loading of the Leaflet Javascript library and CSS files
  3. The asynchronous request for the list of accounts

The order in which these operations complete is unpredictable. Rudimentary testing can lead you to wrong assumptions. The fact is: you need to assume that these operations can complete in any order. For example:

  1. In jsLoaded, you can’t just draw the map inside the component’s “map” div because you can’t assume that the component has already been rendered and therefore that the div already exists in the DOM.
  2. In accountsChangeHandler, you can’t just add markers to the map because you can’t assume that the map has already been drawn (the Leaflet library may still be loading).

Solution

  1. If you need to directly manipulate DOM elements in your component, you should do it in the component’s renderer. This is where the code to draw the map inside the “map” div should go. Let’s override the default rererender() function of the renderer as follows:
  2. In jsLoaded, we simply force the component to rerender():
  3. To load the markers, we create a helper function (because it will be called from different places). Before adding the markers, we check that the map exists. If it doesn’t, we don’t do anything.
  4. In accountsChangeHandler(), we call addMarkers().

    If the map is already available, the markers will be added to it. If it’s not, nothing will happen (see previous step) and we’ll have to make sure the markers are added after the map is created (see next step).

  5. At the end of the rerender() function, we add a call to addMarker() to add the markers in case we received the accounts before the map was rendered (accountsChangeHandler was triggered before jsLoaded)

Summary and Source Code

Components that rely on different operations happening in parallel need to be designed carefully to avoid unintended behaviors. Click here to install the application in your developer org using an unmanaged package, and I’m looking forward to your feedback. You will need to add location information (latitude and longitude) to a few accounts for the component to be useful.

Get the latest Salesforce Developer blog posts and podcast episodes via Slack or RSS.

Add to Slack Subscribe to RSS