I'm working on a complex LWC with several nested child components. I’m passing a public property decorated with @api from the parent to the child, but when the parent’s data changes, the child doesn't seem to re-render or pick up the new value. I’ve tried using @track on the parent side, but it’s still inconsistent. Is there a specific lifecycle hook or a setter/getter pattern I should be using to ensure reactivity?
3 answers
This is a classic "shallow vs. deep" reactivity issue. In LWC, @api properties are reactive, but if you are passing an object or an array, the framework only checks the reference, not the internal properties. If you modify a property inside an object without changing the object reference itself, the child won't detect a change. To fix this, you should either use a setter for your @api property in the child component to manually trigger logic, or use the "spread operator" in the parent to create a new object reference (e.g., this.myObj = { ...this.myObj, field: newValue }). This forces the engine to recognize a change and push the update to the child.
Are you perhaps using the 'connectedCallback' to initialize data that depends on that @api property? Since 'connectedCallback' only runs once when the component is inserted into the DOM, it won't re-run when the property changes later. Have you tried moving that logic into a renderedCallback or using a getter to compute the values dynamically?
Remember that @track is no longer required for simple fields in modern LWC (since Spring '20), but it is still essential for observing internal changes in objects and arrays.
I agree with Thomas. A lot of developers still over-use @track out of habit. Understanding that the framework is now reactive by default for primitive types saves a lot of unnecessary code.
Robert, you're spot on! I was doing exactly that—setting a local variable in 'connectedCallback'. I switched to using a setter for the @api property as Linda suggested, and now I can execute the initialization logic every time the data flows down from the parent. It’s much cleaner than trying to force a refresh through 'renderedCallback' which can cause infinite loops if you aren't careful.