Skip to main content

Mastering LWC Decorators: @api, @track, and @wire Explained

๐Ÿ’ฌ In Plain Words: Three decorators, three specific jobs. @api makes a property public so the parent component can control it. @track forces a re-render when you change data inside an object or array. @wire automatically pulls data from Salesforce and refreshes whenever its inputs change. Everything else is just a plain field—and remember, plain fields in modern LWC are already reactive!

⚡ Key Points

  • All plain fields are reactive: Since Spring '20, you do not need a decorator just to make a field update the UI when its value is reassigned.
  • @api is for Parent-to-Child communication: It exposes a property or method publicly. The child component should never mutate its own @api properties.
  • @track is for deep reactivity: Use it only when you intend to mutate the internal properties of an array or object without completely reassigning it.
  • @wire is a continuous subscription: It fetches data declaratively. If you prefix a parameter with a $, the wire automatically re-runs whenever that parameter changes.

๐Ÿ—️ Core Concept: The Big Three Decorators

To write clean Lightning Web Components (LWC), you must start with the default behavior: every plain field is reactive on reassignment. If you write this.searchKey = 'New Value', the template automatically re-renders. You do not need a decorator for this.

So, when do you actually use decorators? Let's break it down:

  • @api: Marks a property or method as PUBLIC. The parent component passes data into the child via markup using kebab-case (e.g., record-id in HTML maps to recordId in JS). Crucially, the child component must treat @api properties as read-only.
  • @track: Tells the framework to observe changes inside an object or array. For example, this.filters.region = "North" will not trigger a re-render on an untracked object. However, a better and more modern practice is to simply reassign the object using the spread operator: this.filters = { ...this.filters, region: "North" }. Because reassignment is cleaner, @track is rarely needed today.
  • @wire: Binds a property or function directly to a data source (like a cacheable Apex method or a UI API adapter). The data returned by a wire is immutable (read-only), so you must clone it if you want to modify it.
๐Ÿ“Œ Example: The Right Way to Handle Arrays
Suppose a child component has @api contacts = [].

The Wrong Way: The child executes this.contacts.push(newContact). This causes a bug where the UI doesn't re-render, and it illegally mutates the parent's array.

The Right Way: The child fires a CustomEvent. The parent listens for the event, adds the new contact to its own list, and the updated array naturally flows back down to the child via the @api property.
๐Ÿงญ 360 Card — Decorators Overview
Rule: @api means the parent owns it. @track means you mutate inside. @wire means the Salesforce platform feeds it.
Gain: Three simple words that instantly tell a developer who owns a property and where its value originates.
Price: @api properties are read-only in the child. Breaking this rule (like pushing to a public array) causes hard-to-trace bugs.
Limits: Plain fields handle 90% of reactivity needs. @wire requires a $ prefix for parameters to be dynamic.
At Volume: Reassigning massive arrays on every keystroke can hurt performance. Always debounce user inputs before updating your lists.
⚠ INTERVIEW TRAP: Wire Subscriptions
A common mistake is thinking @wire is a one-time data load. It is not—it is an active subscription. Furthermore, if you forget the dollar sign prefix for a dynamic parameter (e.g., passing 'recordId' instead of '$recordId'), the parameter is captured as a literal string once and will never re-fire when the record ID changes.

๐ŸŽฏ Core Q&A

Q: Walk me through @api, @track, and @wire. Which one do you actually reach for, and how often?

๐ŸŽฏ Say this first: @api is what the parent owns, @track is for deep internal mutations, and @wire is data fed by the platform.

A: Let's look at how often they are used in modern LWC:

  • @api (Constantly): Used anytime a parent needs to pass data down or call a public method on a child.
  • @wire (Often): Used for declarative, cacheable data fetching from Apex or standard Salesforce UI APIs.
  • @track (Almost Never): Because modern LWC treats all standard fields as reactive upon reassignment, it's almost always better to create a new object/array copy rather than tracking deep mutations.

๐Ÿ” Follow-ups (Scenario-Based)

Q1: A child component executes this.items.push(newRow) on an @api array. Nothing re-renders, and the parent's data quietly changes too. Explain both bugs.

A: There are two massive architectural violations here:

  • The Render Bug: push() mutates the array in place. Because the property wasn't entirely reassigned, an untracked property never detects a change, so the template fails to re-render.
  • The Data Flow Bug: @api properties are passed by reference. By mutating it, the child has illegally reached into the parent's state. This breaks the "data flows down, actions flow up" rule of LWC. The correct pattern is for the child to dispatch an event, letting the parent handle the data update.

Q2: Your @wire needs to filter by a recordId that arrives from the Lightning record page. How do you make it react, and what happens on the first render?

A: You make the parameter reactive by prefixing it with a dollar sign: @wire(getContacts, { accountId: '$recordId' }). This tells the wire to re-fire every time this.recordId changes.

  • First Render Danger: Before the page fully loads, recordId might be undefined. The wire will fire immediately with that undefined value.
  • The Fix: You must guard your Apex method to tolerate null inputs (returning an empty list) or use a getter in your JS to prevent the wire from executing until the ID is fully populated.
๐Ÿง  Core Takeaway: Data flows down via @api. Actions flow up via Custom Events. Plain fields handle 90% of your UI reactivity. Save @track for complex nested objects, and rely on @wire for effortless, reactive data fetching.