Reactive Properties

If the value of a reactive property changes, the component re-renders. When a component re-renders, all the expressions used in the template are re-evaluated.

All public properties are reactive. To mark a property as public, annotate it with @api.

To mark a private property as reactive, annotate it with @track. Private reactive properties are called tracked properties.

A property can have only one decorator.

Tip

Decorators are a JavaScript language feature. The @api and @track decorators are unique to Lightning Web Components.

Public Properties

To expose a public property, decorate it with @api. Public properties define the API for a component. An owner component that uses the component in its markup can access the component’s public properties.

Public properties are reactive. If the value of a reactive property changes, the component’s template re-renders.

Import the @api decorator from lwc.

import { LightningElement, api } from 'lwc';

Here’s an example of a TodoItem class with an itemName public property. This class is part of a example-todo-item component, where example is the namespace.

// todoItem.js
import { LightningElement, api } from 'lwc';
export default class TodoItem extends LightningElement {
    @api itemName = 'New Item';
}

The component’s template defines a single todo item.

<!-- todoItem.html -->
<template>
    <div>
        <label>{itemName}</label>
    </div>
</template>

A parent component, in this case example-todo-app, can set the itemName property on child example-todo-item components.

Property names in JavaScript are in camel case while HTML attribute names are in kebab case (dash-separated) to match HTML standards. In the example-todo-app template, the item-name attribute on example-todo-item maps to the itemName JavaScript property on example-todo-item.

<!-- todoApp.html -->
<template>
    <div>
        <example-todo-item item-name="Milk"></example-todo-item>
        <example-todo-item item-name="Bread"></example-todo-item>
    </div>
</template>

The parent component can also access and set the itemName property in JavaScript.

// todoApp.js
const myTodo = this.template.querySelector('example-todo-item');
myTodo.itemName // New Item

A component that declares a public property can set only its default value. In our example, the example-todo-item component can’t update the value of the itemName property in the todoItem.js file.

Tracked Properties

To track a private property’s value and render a component when it changes, decorate the property with @track.

You can use a tracked property directly in a template. You can also use a tracked property indirectly in the getter of a property that’s used in a template.

Import @track from the lwc module.

import { LightningElement, track } from 'lwc';

Tip

Track a property only if you want the component to re-render when the property’s value changes. Don’t track every private property.

Let’s look at a simple component that displays an item name, then we’ll jump into something just a notch more complicated.

When the component first renders, it displays the word milk above a Change Item button. When a user clicks Change Item, the handleClick function executes and changes itemName from milk to bread. It also prints itemName is bread in the browser’s console.

What do you think happens if we comment out @track in trackSimple.js? Let’s try it.

    // @track
    itemName = 'milk';

Click Change Item. The component doesn't re-render, so we still see milk. However, in the browser console, we see itemName is bread, because the value of itemName did change.

In a new playground, let’s look at a parent component, api-property, that passes data to a child component, chart-bar, using the child's public percentage property. Select chartBar.js to see the public property.

In the playground, use the input field to increase and decrease the size of the chart bar.

The api-property component also has a percentage property, but it isn't public, it's tracked. When the number in the input field changes, handlePercentageChange in apiProperty.js executes and assigns the new value to this.percentage.

In apiProperty.html, the tracked percentage property is bound to the public percentage property on the child chart-bar component. The parent uses the property to pass a value down to the child.

<example-chart-bar percentage={percentage}></example-chart-bar>

The api-property component re-renders when the tracked value changes. If you remove @track and change the value in the input field, the chart bar doesn't change size. Components that consume api-property can’t set the value, because tracked properties are private.

Example

You can also view the apiProperty and chartBar components in the Lightning Web Components recipes app.

Data Types

There are some limitations on the depth of changes tracked for re-rendering in reactive properties. The tracking depth depends on the type of the reactive property.

Lightning Web Components tracks changes to the internal values of these types of reactive properties:

This behavior is subtle so let’s look at some code. This class has a reactive property, x, which is an object.

// trackObject.js
import { LightningElement, track } from 'lwc';
export default class TrackObject extends LightningElement {
    @track x = {
        a : "",
        b : ""
    };

    init() {
        this.x.a = "a";
        this.x.b = "b";
    }

    update() {
        this.x.a = "aa";
        this.x.b = "bb";
    }
}

The template has a few buttons that change the internal state of x. The onclick handlers for the buttons are wired to the init() and update() methods in the JavaScript file.

<!-- trackObject.html -->
<template>
    <p>object prop: {x.a}</p>
    <p>object prop: {x.b}</p>

    <button onclick={init}>Init</button>
    <button onclick={update}>Update</button>
</template>

When you click either button, the updated values of the member values of x are re-rendered. Changes to x.a and x.b are tracked because x is a plain object.

Now, let’s look at a similar component with a reactive property, x, of type Date.

// trackDate.js
import { LightningElement, track } from 'lwc';
export default class TrackDate extends LightningElement {
    @track x;

    initDate() {
        this.x = new Date();
    }

    updateDate() {
        this.x.setHours(7);
    }
}

Similarly to our previous example, the template has a few buttons that change the internal state of x.

<!-- trackDate.html -->
<template>
    <p>Date: {x}</p>

    <button onclick={initDate}>Init</button>
    <button onclick={updateDate}>Update</button>
</template>

When you click the Init button, the change is tracked and the template is re-rendered. Lightning Web Components can track that x is pointing to a new Date object. However, when you click Update, the template is not re-rendered. Lightning Web Components doesn’t track changes to the value of the Date object.

Note

When you set a reactive property to a value that can’t be tracked, a warning is logged. If you’re trying to debug a scenario where the component isn’t re-rendering on change, look in your browser console For our example, the browser console logs this helpful warning:

Property "x" of [object:vm TrackDate] is set to a non-trackable object,
which means changes into that object cannot be observed.

Getters and Setters

When a component receives data, it performs two basic operations: storing and reacting. In the simplest case, you can declare your @api property and be done.

<template>
    <h1>Greetings, {message}.</h1>
</template>
export default class LightningHello extends LightningElement {
    @api message;
}

However, you probably want to do more interesting things with the data, such as normalizing it or modifying it. Properties are immutable, so how we do that without breaking the data flow model?

Let’s say you are being passed an array and you have to display the list of items with modified data.

const items = [
    {label : "item1"},
    {label : "item2"},
    {label : "item3"}
 ];

The markup iterates over the array and displays the items. The for:each directive requires a key value for each item in a list.

<template>
    <ul>
        <template for:each = {state.items} for:item = "item">
            <li key={item.key}>
                {item.label}
            </li>
        </template>
    </ul>
</template>

To modify the data to add a value for the key property, use this pattern.

export default class LightningList extends LightningElement {
    @track state = {};
    privateItems = {};

    @api
    get items() {
        return this.privateItems;
    }

    set items(items) {
        this.privateItems = items;

        this.state.items = items.map( item  => {
            return {
               label : item.label ,
               key: generateUniqueId()
            }
        });
    }
}

The original value is stored before the state object is modified for the template.

Manage Attribute Dependencies in a Getter

An attribute in HTML turns into a property assignment in JavaScript. In both cases, the order of assignment is not guaranteed. To check for the existence of other attributes use a getter. Don’t use an @api setter that relies on a value from another @api property.

Use a getter reference in the template (not the @api getter).

Let’s assume we have a datatable component that displays a check mark on selected rows. We have two separate attributes rows and selectedRows, which have a dependency on the other.

<template>
    <example-datatable selected-rows="1,2" rows="1,2,3,4"></example-datatable>
</template>

Since the order in which the attributes are received isn’t guaranteed, use getters to check the dependency.

export default class Datatatable extends LightningElement {
    @track state = {};

    @api
    get rows() {
        return this.state.rows;
    }

    set rows(value) {
        this.state.rows = value;

        // Check to see if the rows have
        // been marked as selected.
        if (this.state.selectedRows && !this.selectedRowsSet) {
            this.markSelectedRows();
            this.selectedRowsSet = true;
        }
    }

    @api
    get selectedRows() {
         return this.state.selectedRows;
    }

    set selectedRows(value) {
        this.state.selectedRows = value;

        // If rows haven’t been set,
        // then we can't mark anything
        // as selected.
        if (!this.state.rows) {
            this.selectedRowsSet = false;
            return;
        }

        this.markSelectedRows();
    }

    markSelectedRows() {
        // Mark selected rows.
    }
}

Using getters and setters ensures that the public API contract is easily enforced. Don’t change the value of a property that’s annotated with @api.

Normalize data in the setter if something depends on that value at set time, for example, to append a CSS class programmatically on an element. Return the original value in the getter. Normalization can also be done in the getter so that the template has access to a value even if the consumer doesn’t set anything.

@track state = {
    selected : false
};

privateSelected = 'false';

@api
get selected() {
    return this.privateSelected;
}
set selected(value) {
    this.privateSelected = value;
    this.state.selected = normalizeBoolean(value)
}