# Reactivity
All fields are reactive. If the value of a field changes and the field is used in a template or in the getter of a property used in a template, the component re-renders and the renderedCallback()
lifecycle hook is called. When a component re-renders, all the expressions used in the template are re-evaluated.
To make a field public and therefore available to a component's consumers as a property, decorate it with @api
.
Field and property are almost interchangeable terms. A component author declares fields in a class. An instance of the class has properties, so to component consumers, a field is a property. In a Lightning web component, only fields that a component author decorates with @api
are publicly available to consumers as object properties.
Tip
Decorators are a JavaScript language feature. The @api
and @track
decorators are unique to Lightning Web Components. A field can have only one decorator.
For a list of properties available in the LightningElement
class, see Inherited Properties and Methods.
# Public Property Reactivity
To expose a public property, decorate a field with @api
. Public properties define the API for a component. An owner component that uses the component in its HTML markup can access the component’s public properties via HTML attributes.
If the value of a public property changes, the component’s template re-renders.
Let's look at a simple app in the playground. The example-todo-item
component has a public itemName
property. The example-todo-app
component consumes example-todo-item
and sets its property via the item-name
attribute.
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
.
In todoApp.html, change the value of the item-name
attributes and watch the component re-render.
Let's walk through the code.
The TodoItem
class imports the @api
decorator from lwc
. It declares an itemName
field and decorates it with @api
to make it public. This class is part of the 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.
<!-- 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
# Field Reactivity
All fields are reactive. When the framework observes a change to a field used in a template or used in the getter of a property used in a template, the component re-renders.
In this example, the firstName
and lastName
fields are used in the getter of the uppercasedFullName
property, which is used in the template. When either field value changes, the component re-renders.
The firstName
and lastName
fields contain primitives.
firstName = '';
lastName = '';
Note
Fields are reactive. Expandos, which are properties added to an object at runtime, are not reactive.
# Reactivity Considerations
Although fields are reactive, the LWC engine tracks field value changes in a shallow fashion. Changes are detected when a new value is assigned to the field by comparing the value identity using ===
. This works well for primitive types like numbers or boolean.
import { LightningElement } from 'lwc';
export default class ReactivityExample extends LightningElement {
bool = true;
number = 42;
obj = { name: 'John' };
checkMutation() {
this.bool = false; // Mutation detected
this.number = 42; // No mutation detected: previous value is equal to the newly assigned value
this.number = 43; // Mutation detected
this.obj = { name: 'John' }; // Mutation detected: redefining the object with the same value creates a new object so it's not ===
this.obj.name = 'Bob'; // No mutation detect: `obj` field value is not reassigned
this.obj = { ...this.obj, title: 'CEO' } // Mutation detected
}
}
When manipulating complex types like objects and arrays, you must create a new object and assign it to the field for the change to be detected.
To avoid such issues when working with complex objects, use the @track
decorator to deeply tracks mutations made to the field value.
# Track Changes Inside Objects and Arrays
Decorate the field with @track
to observe changes to the properties of an object or to the elements of an array.
When a field is decorated with @track
, Lightning Web Components tracks changes to the internal values of:
- Plain objects created with
{}
- Arrays created with
[]
The framework observes mutations made to plain objects and arrays in a recursive fashion, including nested objects, nested arrays, and a mix of objects and arrays. Cyclic references are also handled.
However, the framework doesn't observe mutations made to complex objects, such as objects inheriting from Object
, class instances, Date
, Set
, or Map
.
# Observe an Object's Properties
To tell the framework to observe changes to the properties of an object, decorate the field with @track
.
Note
As discussed earlier, without using @track
, the framework observes changes that assign a new value to the field. If the new value is not ===
to the previous value, the component rerenders.
Let's declare the fullName
field, which contains an object with two properties. The framework observes changes that assign a new value to the fullName
field.
fullName = { firstName : '', lastName : ''};
This code changes the value of fullName
, so the component re-renders.
this.fullName = { firstName: 'Jane', lastName: 'Doe' };
This code changes the value of firstName
. The framework is not observing changes to firstName
, so the component doesn't re-render. Remember, the framework is observing changes to the fullName
field. This code doesn't assign a new value to fullName
, instead it assigns a value to the firstName
property.
this.fullName.firstName = 'Jane';
To tell the framework to observe changes to the properties of the fullName
object, decorate the field with @track
.
@track fullName = { firstName: '', lastName: '' };
Now when the firstName
property changes, the component re-renders.
this.fullName.firstName = 'Jane';
Let's look at this code in the playground. Enter a first name and last name and watch the component re-render. Now remove @track
and do the same. The component doesn't re-render.
# Rerender an Object with New Properties
A component rerenders only if a property accessed during the previous rendering cycle is updated, even when the object is annotated with @track
. This prevents the component from rerendering excessively,
Consider this tracked object and a getter that prints the object’s properties.
@track obj = {value1: 'Hello'};
get words() {
return Object.entries(this.obj)
.map(([key, value]) => ({key, value}));
}
During the first render cycle, the framework records that obj.value1
is accessed. Any mutation to obj
that doesn’t affect value1
is ignored since it doesn’t impact the rendered content. Therefore, a change to value1
triggers a rerendering, but adding a new property to obj
or a change to value2
doesn’t trigger a rerendering.
// Component rerenders.
setValue1(e) {
this.obj.value1 = 'Hello World';
}
// Component doesn’t rerender.
setValue2(e) {
this.obj.value2 = 'Hello LWC';
}
To rerender your component when adding a new property, assign the object to a new object with both values.
setValue2(e) {
this.obj = {
...this.obj,
value2: 'Hello LWC'
};
}
# Observe an Array's Elements
Another use case for @track
is to tell the framework to observe changes to the elements of an array.
If you don’t use @track
, the framework observes changes that assign a new value to the field.
arr = ['a','b'];
The component rerenders when you assign a new value to arr
.
// Component rerenders.
this.arr = ['x','y','z'];
However, if we update or add an element in the array, the component doesn’t rerender.
// Component doesn’t rerender.
this.arr[0] = 'x';
this.arr.push('c');
To tell the framework to observe changes to the array’s elements, decorate the arr
field with @track
Additionally, the framework doesn’t automatically convert the array to a string for updates to the array’s elements. To return the updated string, use a getter to convert the elements of the array to a string using join()
.
@track arr = ['a','b'];
get computedArray() { return this.arr.join(','); }
update() {
this.arr[0] = 'x';
this.arr.push('c');
}
# Observe Complex Objects
Note
As of LWC v1.1.0, @track
is no longer required to make a field reactive unless the field contains an object or an array.
Let’s look at a component with a field, x
, of type Date
. The template has a few buttons that change the internal state of x
. This example highlights that new Date()
creates an object, but not via {}
, so the internals of the object aren't observed, even though the code uses @track
.
When you click the Init button, the x
field is assigned a new Date
object and the template re-renders. However, when you click Update, the template doesn't re-render because the framework doesn’t track changes to the value of the Date
object.
To ensure that the template is rerendered when the value changes, clone the existing date and update its value.
Note
When you set a 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 rerendering 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.