# Wire Adapters

Note

Building a custom wire adapter is not supported on the Salesforce Platform. See LWC Open Source.

LWC has an elegant way to provide a stream of data to a component. Define a data provider called a wire adapter. A wire adapter simply provides data. A wire adapter doesn't know anything about the components that it provides data to.

In a component, declare its data needs by using the @wire decorator to connect (or wire) it to a wire adapter. In this example, the component is wired to the getBook wire adapter, which we can assume provides details about a specific book. This declarative technique makes component code easy to read and reason about.

import { LightningElement } from 'lwc'; export default class WireExample extends { @api bookId; @wire(getBook, { id: '$bookId'}) book; } 

Wire adapters are part of LWC's reactivity system. An @wire takes the name of a wire adapter and an optional configuration object. You can use a $ to mark the property of a configuration object as dynamic. When a dynamic property’s value changes, the wire adapter's update method executes with the new value. When the wire adapter provisions new data, the component rerenders.

Another reason to use wire adapters is that they're a statically analyzable expression of data. Static analysis tools find issues before you run code and can increase code quality and security.

Note

The @wire delegates control flow to the Lightning Web Components engine. Delegating control is great for read operations, but it isn’t great for create, update, and delete operations. As a developer, you want complete control over operations that change data.

# Syntax to Implement a Wire Adapter

A wire adapter is a class that implements the WireAdapter interface, and is a named or default export from an ES6 module.

When a component is constructed, if it has an @wire to a wire adapter class, an instance of the wire adapter class is constructed. The wire adapter invokes the DataCallback function to provision data to the component (into the wired field or function).

If the @wire configuration object changes, the wire adapter's update(newConfigValues) is called. The wire adapter fetches the necessary data then calls dataCallback(newValueToProvision) to provision the value to the component.

interface WireAdapter { update(config: ConfigValue); connect(); disconnect(); } interface WireAdapterConstructor { new (callback: DataCallback): WireAdapter; } type DataCallback = (value: any) => void; type ConfigValue = Record<String, any>; 

The wire adapter's code shouldn’t be aware of the components to which it provides data. The wire adapter simply implements this interface to produce a stream of data.

# Syntax to Consume a Wire Adapter

To consume a wire adapter, decorate a field or function with @wire. The @wire takes a wire adapter and optionally a configuration object. The data is provisioned into the wired field or function.

import { LightningElement } from 'lwc'; import { adapterId } from 'adapterModule'; export default class WireExample extends LightningElement { @wire(adapterId[, adapterConfig]) fieldOrFunction; } 

A configuration object can reference a property of the component instance that's declared as a class field. In the configuration object, prefix the property with $, which tells LWC to evaluate it as this.propertyName. The property is now dynamic; if its value changes, the update method of the adapter executes. When new data is provisioned, the component rerenders. Use the $ prefix for top-level values in the configuration object. Nesting the $ prefix, such as in an array like ['$myIds'], makes it a literal string.

Don’t update a configuration object property in renderedCallback() as it can result in an infinite loop.

Important

Objects passed to a component are read-only. To mutate the data, a component should make a shallow copy of the objects it wants to mutate. It’s important to understand this concept when working with data. See Data Flow Considerations.

# Context Providers

Note

Context providers are not supported on the Salesforce Platform. See LWC Open Source.

Context providers allow contextual state to be shared among all components in a section of the DOM tree without the need for "prop-drilling" (i.e. passing the same prop through multiple layers of components). This is similar to useContext in React or simply using DOM events to communicate between ancestors and descendants.

To create a context provider, use the createContextProvider API:

import { createContextProvider } from 'lwc'; const contextualizer = createContextProvider(WireAdapter); 

Passing a WireAdapter (as described above) into createContextProvider results in a contextualizer. This contextualizer can then be invoked in the connectedCallback of the provider component:

export default class ProviderComponent extends LightningElement { connectedCallback() { contextualizer(this, { consumerConnectedCallback(consumer) { consumer.provide({ value: 'contextual value' }); } }); } } 

Note the consumerConnectedCallback above, which is fired when a context consumer connects to the DOM.

This provider component should contain the consumer component somewhere in its descendant tree:

<!-- providerComponent.html --> <template> <x-consumer-component></x-consumer-component> </template> 

The consumer component can then receive the value from the context provider via @wire:

export default class ConsumerComponent extends LightningElement { @wire(WireAdapter) value; } 

This WireAdapter should, at a minimum, be defined like so:

class WireAdapter { static contextSchema = { value: 'required' // or 'optional' }; constructor(callback) { this._callback = callback; } connect() { /* noop */ } disconnect() { /* noop */ } update(_config, context) { this._callback(context.value); } } 

The contextSchema above describes which values are expected to be supplied in the context (e.g. value in this example). These values are recommended to be marked either 'optional' or 'required'. (These are purely for documentation purposes; they do not affect the runtime behavior.)

The connect and disconnect callbacks above will be invoked when the consumer component connects or disconnects from the DOM. Whereas update will be called whenever the provider calls consumer.provide().

The _config above is optional, and corresponds to any configuration options passed in to the @wire by the consumer, like so:

export default class ConsumerComponent extends LightningElement { @wire(WireAdapter, { some: 'config' }) value; } 

In this example, the _config would be the object { some: 'config' }.

If no config is provided to @wire (as in the prior example), then the _config passed to the WireAdapter above will be an empty object ({}).

Putting it all together, the value property should be populated with the string "contextual value", as shown in the following example:

# Example: RCast App

The RCast app is a PWA podcast player written with Lightning Web Components.

https://github.com/pmdartus/rcast

RCast uses a wire adapter called connectStore to provision data to its components.

export class connectStore { dataCallback; store; subscription; connected = false; constructor(dataCallback) { this.dataCallback = dataCallback; } connect() { this.connected = true; this.subscribeToStore(); } disconnect() { this.unsubscribeFromStore(); this.connected = false; } update(config) { this.unsubscribeFromStore(); this.store = config.store; this.subscribeToStore(); } subscribeToStore() { if (this.connected && this.store) { const notifyStateChange = () => { const state = this.store.getState(); this.dataCallback(state); }; this.subscription = this.store.subscribe(notifyStateChange); notifyStateChange(); } } unsubscribeFromStore() { if (this.subscription) { this.subscription(); this.subscription = undefined; } } } 

# Example: Book List App

This simple app is an editable list of books. You can create, edit, and delete book titles.

<!-- app.html --> <template> <c-book-create onbookcreate={handleCreateBook}></c-book-create> <c-book-list oneditbook={handleEditBook} ondeletebook={handleDeleteBook} ></c-book-list> <template lwc:if={inEditMode}> <c-book-edit book-id={editBookId} onsaveedit={handleSaveEdition} oncanceledit={handleCancelEdition} ></c-book-edit> </template> </template> 

When you click to edit a book title, an input field appears with Save and Cancel buttons.

<!-- bookEdit.html --> <template> <input type="hidden" name="id" value={bookId} /> <input name="title" value={draftTitle} onchange={handleTitleChange} /> <button onclick={handleSave}>Save</button> <button onclick={handleCancel}>Cancel</button> </template> 

When the book-edit component is constructed, the @wire provisions the data from the getBook wire adapter. Because the adapter config $bookId is prefixed with $, when its value changes, the @wire provisions new data and the component rerenders.

// bookEdit.js import { LightningElement, api, wire } from 'lwc'; import { getBook } from 'c/bookApi'; export default class BookEdit extends LightningElement { @api bookId; draftTitle = ""; @wire(getBook, { id: '$bookId'}) bookDetails(book) { if (book === null) { console.error("Book with id %s does not exist", this.bookId); } this.draftTitle = book.title; }; handleTitleChange(event) { this.draftTitle = event.target.value; } handleSave() { this.dispatchEvent( new CustomEvent('saveedit', { detail: { id: this.bookId, title: this.draftTitle } }) ); } handleCancel() { this.draftTitle = ""; this.dispatchEvent( new CustomEvent('canceledit') ); } } 

The wire adapters for this app are defined in bookApi.js.

// bookApi.js import { bookEndpoint } from './server'; const getBooksInstances = new Set(); function refreshGetBooksInstances() { getBooksInstances.forEach(instance => instance._refresh()); } export class getBooks { constructor(dataCallback) { this.dataCallback = dataCallback; this.dataCallback(); } connect() { getBooksInstances.add(this); } disconnect() { getBooksInstances.remove(this); } update() { this._refresh(); } _refresh() { const allBooks = bookEndpoint.getAll(); this.dataCallback(allBooks); }; } export class getBook { connected = false; bookId; constructor(dataCallback) { this.dataCallback = dataCallback; } connect() { this.connected = true; this.provideBookWithId(this.bookId); } disconnect() { this.connected = false; } update(config) { if (this.bookId !== config.id) { this.bookId = config.id; this.provideBookWithId(this.bookId); } } providBookWithId(id) { if (this.connected && this.bookId !== undefined) { const book = bookEndpoint.getById(id); if (book) { this.dataCallback(Object.assign({}, book)); } else { this.dataCallback(null); } } } } export function createBook(title) { bookEndpoint.create(title); refreshGetBooksInstances(); } export function deleteBook(id) { bookEndpoint.remove(id); refreshGetBooksInstances(); } export function updateBook(id, newTitle) { bookEndpoint.update(id, newTitle); refreshGetBooksInstances(); } 

This app also includes an abstraction of server code.

// server.js  // A server abstraction class BookEndpoint { bookStore = new Map(); nextBookId = 0; getAll() { return this.bookStore.values() } getById(id) { return this.bookStore.get(parseInt(id)); } create(title) { const book = { id: this.nextBookId++, title, }; this.bookStore.set(book.id, book); } update(id, title) { const book = { id: parseInt(id), title }; this.bookStore.set(book.id, book); } remove(id) { this.bookStore.delete(parseInt(id)); } } export const bookEndpoint = new BookEndpoint(); bookEndpoint.create('The Way of Kings'); 

(The component Book List app also includes book-list and book-create components. These components use the same techniques, so we've omitted them for space.)