Introducing Lightning Web Components Recipes, Patterns and Best Practices

Recipes is a new sample application that provides a collection of easy-to-digest code samples for Lightning Web Components. In this blog post, we walk through some key recipes and highlight patterns and best practices along the way.

Exploring Recipes, one of the new apps in the sample gallery, is a great way to get started with the new Lightning Web Components announced last week. From Hello World to data access, the app features over 50 recipes that demonstrate how to code specific tasks in 30 lines of code or less. Let’s dive in and take a closer look at some of them.

Hello

Hello, the first recipe, shows how to bind a property to an HTML element using {}.

Like many modern frameworks, Lightning Web Components enforce one way data-flows and doesn’t support bidirectional data binding (which often leads to hard-to-follow and error-prone state transitions). The HelloBinding recipe demonstrates the generic pattern used to keep an input field in sync with a component property using a one-way data flow: bind the input field value to the property, and register an onchange event listener that updates the value of the property on user input.

Apex

The Apex tab features recipes that demonstrate how to call Apex methods in Lightning web components. Apex methods are imported as ECMAScript modules. For example:

import getContactList from '@salesforce/apex/ContactController.getContactList';

Once imported, you can invoke an Apex method using two different approaches:

  1. Imperatively (apexImperativeMethod recipe): You call the imported method like any other asynchronous function in JavaScript. The function call returns a promise.
  2. Using @wire (apexWireMethodWithParams recipe): You use the @wire decorator to wire the imported method to a component property or function. The wired method is automatically invoked when parameter values are initialized, and is invoked again whenever parameter values change.

Best practice: Prefer @wire over imperative Apex method invocation. @wire fits nicely in the overall Lightning Web Component reactive architecture. We are also building some performance enhancement features that are only available with @wire. There are a few use cases, however, that require you to use imperative Apex. For example, when you need to invoke an Apex method without responsive parameters in response to a specific event, like a button click in the apexImperativeMethod recipe mentioned above.

Now that we know @wire is the recommended approach in most cases, there are two ways to wire an Apex method:

  1. Wire the Apex method to a property (apexWireMethodToProperty recipe)
  2. Wire the Apex method to a function (apexWireMethodToFunction recipe)

Best practice: Prefer wiring to a property. This best practice applies to @wire in general (not just to wiring Apex methods). It makes the code less verbose and defines a predictable response pattern to work with myProperty.data and myProperty.error. There are a few use cases, however, that require you to wire to a function. The most common one is data transformation (wireGetPicklistValuesByRecordType recipe).

Lightning Data Service

The Data Service tab features recipes to create, update, and delete records using the Lightning Data Service createRecord, updateRecord, and deleteRecord functions.

Best practice: Prefer the Lightning Data Service functions (createRecord, updateRecord, and deleteRecord) over invoking Apex methods to create, update and delete single records. Benefits: You don’t have to write Apex code, and, most importantly, the Lightning Data Service updates the local record cache, ensuring consistency across components showing the same data.

Navigate to a contact record page to see additional recipes to view and edit a record.

Best practice: When building a form-type user interface:

  1. Always consider lightning-record-form (recordFormDynamicContact recipe) first. It is the fastest/most productive way to build a form.
  2. If you need more control over the layout, want to handle events on individual input fields, or need to execute pre-submission logic: consider lightning-record-view-form (recordViewFormDynamicContact recipe) or lightning-record-edit-form (recordEditFormDynamicContact recipe).
  3. If you need even more control over the UI, or if you need to access data without a UI: Use @wire(getRecord) (wireGetRecordDynamicContact recipe).

Each recipe on the contact record form comes in two different implementations: using static or dynamic schema references. For example, compare the recordFormStaticContact and recordFormDynamicContact recipes.

Using the static schema approach, you explicitly import required schema artifacts (objects or fields). The major benefit of this approach is that it ensures referential integrity:

  1. The existence of fields and objects is checked at compile time.
  2. Fields and objects that are statically imported cannot be inadvertently deleted from the data model.
  3. Dependent objects and fields are automatically included when creating packages and change sets.

Using the dynamic schema approach, you specify fields as strings when constructing a list of fields. You also reference fields using the dot notation: contact.my_custom_field__c. This approach doesn’t provide referential integrity. For example, the existence of the my_custom_field__c field cannot be checked at compile time, and the deletion of my_field__c cannot be prevented even though it is used in your code. On the plus side, this approach supports object agnostic components, and loose coupling between layers (UI and data model layers) is sometimes a requirement of specific modular application development patterns.

Intercomponent communication

The Child-to-Parent and Parent-to-Child tabs feature recipes that demonstrate how to communicate between components.

Child-to-parent

A child component communicates with its parent component by dispatching a DOM event with or without a data payload (eventWithData recipe and eventSimple recipe respectively).

Best practices:

  • Always use CustomEvent (not Event), even when the event doesn’t have a data payload
  • Prefer passing data using primitive data types in the event payload.
  • If you must pass data using a non-primitive data type in the event payload, pass a copy of the object or array to avoid leaking private objects and unpredictable mutations by the event listener.
  • If you need to pass a record, consider passing the record ID.
  • Prefer setting bubbles to false and composed to false when dispatching events (these are the default values).
  • Bubbling is especially useful when the parent uses an iteration of the child component (eventBubbling recipe).

Parent-to-child

A parent component communicates with a child component by either:

  • Setting the @api properties of the child (apiProperty recipe)
  • Calling an @api function defined in the child component (apiFunction recipe)

Sibling component communication

Unlike Aura, Lightning Web Components do not support application events. A pubsub utility is provided as part of the sample apps to support communication between sibling components assembled in a flexipage (App Builder) where traditional parent/child communication patterns are not available. Check out the pubsubSearchBar and pubsubContactList recipes to learn how to respectively publish and subscribe using the pubsub utility. Unlike application events, the pubsub utility enforces page scoping. We plan to provide an official scoped event mechanism to support sibling component communication in the future (#SafeHarbor).

Best practice:
Do NOT use the pubsub utility for parent/child communication. Use the parent-to-child and child-to-parent patterns described above instead.

Aura interoperability

Aura and Lightning web components coexist and interoperate seamlessly. The Aura Interoperability tab features recipes that demonstrate common integration scenarios:

  • Aura components can include Lightning web components. The Lightning web component child can communicate with its Aura parent by dispatching DOM events (AuraDomEventListener recipe).
  • Aura and Lightning web components can coexist and communicate on the same page. The AuraPubsubPublisher, PubsubContactList and AuraPubsubSubscriber recipes demonstrate how Aura and Lightning web components can communicate using the pubsub utility described above.

Error handling

Recipes throughout the application implement consistent error handling patterns.

Errors that occur when a component loads its state: An error message is displayed “in-place” (within the component itself).

In-place error handling is implemented using the following template pattern:

<template>
    <template if:true={myWiredProperty.data}>
        <!-- UI --> 
    </template>
    <template if:true={myWiredProperty.error}>
        <c-error-panel errors={myWiredProperty.error}></c-error-panel>
    </template>
</template>

A sample error panel component is available here.

Popup dialogs or toasts wouldn’t be advisable in this case because if multiple components on a page fail, the user would see a succession of dialogs, which is a poor user experience. In addition, it would be hard for the user to tell which dialog is related to which component because dialogs or toasts aren’t visually linked to the components that fail.

Errors that occur in response to a user action: either in-place or dialog-based error messages are acceptable.

Best practice: Prefer in-place/inline error messages, especially for errors that occur without user interaction / when components load their state.

Conclusion

We just scratched the surface: there are many more recipes available, showing you how to navigate to different page types, how to use third party libraries like d3 and Chartjs, how to share code between components, etc. We hope you have fun exploring them. Let us know if there are other recipes you’d like to see.

Experience enhanced productivity with web standards, speed, and a compatible and easy-to-use framework with Lightning Web Components. Join us on January 23rd for our live Global Broadcast and learn more — register today!

Resources

Leave your comments...

Introducing Lightning Web Components Recipes, Patterns and Best Practices