Lightning-Web-Components – Passing Read-Only Data in LWC Hierarchies Using @wire

lightning-web-componentslwc-wire-adapter

I have a LWC, which has a number of child LWC, some of which have child LWC on their own etc. Somewhere in a child-child-child… LWC I need a couple of picklists and other data from Salesforce, which I get via @wire, e.g.

import { LightningElement, wire } from 'lwc';
import { getPicklistValues } from 'lightning/uiObjectInfoApi';
import ACCOUNTSOURCE from '@salesforce/schema/Account.AccountSource';

[...]

accountSources;
    
@wire(getPicklistValues, { recordTypeId: '012000000000000AAA', fieldApiName: ACCOUNTSOURCE })
paymentTermsOptions({err, data}) {
    if(data) {
        this.accountSources = data.values;
    } else if(err) {
        console.error('Can not get account picklist: ' + err);
    }
}

(There are a bunch of these, and some of the data requires logic inside if(data).)

However these picklists and some of the other 'wire data' are needed in the top level component for validation purposes too. What is the right thing to do?

Move all these @wire to the top level LWC and pass the retrieved data through to the child-child-child, making use of a getter like…

get picklists() {
    return {
        picklistA: this.accountSources,
        picklistB: this.picklistB
        [...]
    }
}

Or use the same @wire in grand parent and grand child (and trust the first call is cached) with significant code duplication? (Or is there some way to include the same @wire library on two levels of the hierarchy without code duplication?)

Is there a neater alternative? Something I can access both from parent and child, like a "service component (library)"? (I have tried to move decorators outside of LightningElements and not surprisingly failed.)

Best Answer

Locker Service has known to cause performance issues, so passing wire data down through multiple layers will cause significant lag versus duplicating code. Lightning Web Security is much better at this, but it's still generally recommended that you avoid multiple levels of data passing when possible, as getters are not always reactive.

As far as using wire methods, remember that they use the Lightning Data Service as a coherent cache, so even though you're "duplicating code," you're not duplicating the number of server-side calls, and it actually ends up being more efficient for every component to just use the same wire method if they need it.

However, if you want to avoid the duplication of code, you could always just use a template component.

Here's some example code.

// template.js
import { LightningElement, wire } from "lwc";
import DemoWire from 'c/wire'; // LWC OSS Example of a wire

export default class Template extends LightningElement {
  @wire(DemoWire) helloData;
}

// child1.js
import Template from 'c/template';

export default class Child1 extends Template {
}

<template>
  <!-- child1.html -->
  Child 1: {helloData.data}
</template>

// child2.js
import Template from 'c/template';

export default class Child2 extends Template {
}

<template>
  <!-- child2.html -->
  Child 2: {helloData.data}
</template>

Demo.

As you can see, by importing a class that extends LightningElement, we don't have to repeatedly specify the same code over and over again. If you specify an .html template in the template component, you can omit it in the child component, and it will automatically inherit the parent's template. Or, you can specify one template per child, as demonstrated in this example. You can even nest components arbitrarily deep if you want to.

The only requirement for wire is that there must be a VM attached to the component. Notably, "service components" don't have a VM attached to them, so you can't use wire in them. However, a parent component will be part of a child's VM, so the wire methods work as you'd expect.

Of course, you could still use a common, shared component. Just create a component that uses wire, and have that component used as common shared data between all components that use it. This is the "orchestrator" design, where one component orchestrates coordination between otherwise disconnected components.

pubsub is an example of this. It uses a subscribe/publish model to notify components of changes anywhere in the hierarchy. Other designs are also possible, depending on your needs.

Finally, if you want to write a service component, you can. Just note that you can't use wire. You can still call the methods imperatively, and then coordinate a way for other components to be notified of new data updates. It's more "manual", but I prefer this design, as it allows me to control when I want my data to be loaded.

Related Topic