Mutating Objects Without Triggering Reactivity
“LWC tracks property assignment, not deep object mutations.”
What Happened
Built a complex form component for Mobilization Funding's contractor onboarding. Users filled out fields but the Save button's disabled state never updated. The validation logic was correct — I was mutating properties on a tracked object, but LWC's reactivity engine couldn't see the changes. The form looked frozen. Users kept refreshing the page thinking it was broken.
The Wrong Way
// contractorForm.js
import { LightningElement, track } from 'lwc';
export default class ContractorForm extends LightningElement {
@track formData = { name: '', license: '', insurance: false };
handleNameChange(event) {
// Direct mutation - LWC won't detect this change!
this.formData.name = event.target.value;
}
get isValid() {
// This getter never re-evaluates because formData reference didn't change
return this.formData.name && this.formData.license;
}
}The Right Way
// contractorForm.js
import { LightningElement } from 'lwc';
export default class ContractorForm extends LightningElement {
formData = { name: '', license: '', insurance: false };
handleNameChange(event) {
// Spread to create new object reference - triggers reactivity
this.formData = { ...this.formData, name: event.target.value };
}
handleLicenseChange(event) {
this.formData = { ...this.formData, license: event.target.value };
}
get isValid() {
// Now re-evaluates because formData is a new object reference
return this.formData.name && this.formData.license;
}
}The Lesson
LWC reactivity tracks property reassignment, not deep mutations. Spread into a new object to trigger re-renders. @track for deep tracking is deprecated — use the spread pattern.
Enjoyed this? Get more like it.
Glen's Musings — AI, investing, and building things. Occasional. Free.
More LWC Mistakes
Mixing Wire and Imperative Apex Calls for the Same Data
Pick one: reactive wire or imperative call. Mixing them creates ghost data.
Read moreRookieCalling querySelector Before the Component Renders
The DOM doesn't exist in connectedCallback. Stop looking for it there.
Read moreAnnoyingCustom Events Not Reaching Parent Components
CustomEvent doesn't bubble or compose by default. Your parent never heard you.
Read more