0

I have an LWC which acts as a ToolBox: a user selects a tool from the ToolBelt and then a WorkArea is populated with the business logic for that tool.

Components involved:

Parent: Wrapper.js

Child 1: ToolBelt.js

Child 2: WorkArea.js

Where things are working properly: First, Wrapper.js passes a ToolSelection handler down to ToolBelt.js. On select, an event is emitted from ToolBelt.js to Wrapper.js where the selectedTool variable is being updated.

Where things are not working properly: selectedTool is decorated with @api in the parent, @track in the child, and the value is being successfully updated in the parent. But it is not being rerendered in the child component.

Parent.js:

import { LightningElement, api } from 'lwc';
export default class Toolbox extends LightningElement {
@api selectedTool
@api tools = [redacted] 

toolSelectionHandler(event){
    let updatedTools
    let selectedTool;
    const id = event.detail.id 
    const action = event.detail.action
    if( action === 'unselect'){
        updatedTools = this.tools.map( (tool) => {
            tool.selected = false
            return tool
        })
        this.selectedTool = null
    } else{
    updatedTools = this.tools.map( (tool) => {
            if(tool.id === id){
                tool.selected = true
                selectedTool = tool
            } 
            else {
                tool.selected = false
            }
            return tool
        })
        this.selectedTool = selectedTool
    }
    this.tools = updatedTools
}

}

Parent.html:

<template>
<div class="slds-grid slds-wrap slds-grid--pull-padded">
    <div class="slds-p-horizontal--small slds-size--1-of-2 slds-medium-size--1-of-6 slds-large-size--4-of-12" >
        <c-toolbelt 
            tools={tools}
            ontoolselected={toolSelectionHandler}
        ></c-toolbelt>
    </div>
    <div class="slds-p-horizontal--small slds-size--1-of-2 slds-medium-size--5-of-6 slds-large-size--8-of-12">
        <c-work-area 
        selected-tool={selectedTool}
        >
        </c-work-area>
    </div>
  </div>

Leaving out Toolbelt.js and Toolbelt.html because the selection handler is working as expected.

WorkArea.js:

import { LightningElement, track } from 'lwc';

export default class WorkArea extends LightningElement {

@track selectedTool
@track isLoading = false



get tool1(){
    let matchBool
    if(!this.selectedTool){
        matchBool = false
    } else {
        if (this.selectedTool.title = 'tool1') {
            matchBool = true 
        } 
    }
    return matchBool;
}

get tool2(){
    let matchBool
    if(!this.selectedTool){
        matchBool = false
    } else {
        if (this.selectedTool.title = 'tool2') {
            matchBool = true 
        } 
    }
    return matchBool;
}

get tool3(){ 
    let matchBool
    if(!this.selectedTool){
        matchBool = false
    } else {
        if (this.selectedTool.title = 'tool3') {
            matchBool = true 
        } 
    }
    return matchBool;
}

spinnerHandler(){
    this.isLoading = !this.isLoading 
}

}

WorkArea.html:

<template>
<div class="work-area">
    <div if:true={toolOne} class="tool-detail-area selected">
        <c-tool-one 
            onspinnerhandler={spinnerHandler} >
        </c-tool-one>
    </div> 
    <div if:true={toolTwo} class="tool-detail-area selected">
        <c-tool-two
            onspinnerhandler={spinnerHandler} >
        </c-tool-two>
    </div> 
    <div if:true={toolThree} class="tool-detail-area selected">
        <c-tool-three
            onspinnerhandler={spinnerHandler} >
        </c-tool-three>
    </div> 
    <div if:false={selectedTool} class="tool-detail-area default">
        <c-no-tool-display></c-no-tool-display>
    </div>
    <div if:true={isLoading}>
        <c-loading-spinner></c-loading-spinner>
    </div>          
</div>

I've seen a few SO posts about LWC Child components not registering changes made to parent, but most of them are Parent > Child relationship directly. And because events aren't emitted from the child. I haven't seen any where a child modifies state of parent, and tracked variable in sibling isn't re-rendering.

Any help technically or conceptually would be appreciated.

2
  • 1
    why WorkArea's selectTool is just @track and not @api? Do you manage to pass anything at all to the child that way? I'm bit surprised. If it's a primitive (String, number etc) you shouldn't even need track. If it's complex type and even adding @api doesn't work - in parent's event handler try to rebuild the variable, this.selectedTool = JSON.parse(JSON.stringify(this.selectedTool)); and see if it helps Commented Sep 30, 2022 at 20:44
  • Both of your suggestions worked to solve the problem. Why did rebuilding the variable with JSON.parse and JSON.stringify change the behavior of the parent component? Does the variable get converted to a string when its hoisted to parent? Commented Oct 3, 2022 at 17:39

1 Answer 1

1

(Converted from comments / expanded)

Why WorkArea's selectTool is just @track and not @api? Do you manage to pass anything at all to the child that way? I'm bit surprised. If it's a primitive (String, number etc) you shouldn't even need track.

It has to be @api to be part of the component's public interface so I think you setting the value on parent didn't have any effect. That variable was child component's private matter, nice try parent.

in parent's event handler try to rebuild the variable, this.selectedTool = JSON.parse(JSON.stringify(this.selectedTool)); and see if it helps

This one's complicated and reeks of cargo cult programming. I don't know. Rebuilding the variable (object or array of object) sometimes helps the @track / @api realise the value changed and propagate it properly. There's some caching / saving on network roundtrips at play.

https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.reactivity_fields

It shouldn't be needed, @track should be sufficient... But people struggle with it, you can see the trick on some blog posts or

JSON.parse "dance" is OK but of course you'll lose any functions you might have had attached to the object, Dates will flatten to strings... It's quite common to use it when debugging something in JS console and you're getting angry with all the Proxy objects. Array spread operator works OK too for giving the @track/@api a nudge, probably faster execution too.

Another place where it helps is when you need to modify what was sent from Apex. Normally that object is readonly, you need the JSON.parse or spread to make a copy(?). For example good luck using <lightning-tree-grid> with any data coming from server-side. It requires child nodes to be _children but Apex doesn't compile with variable names starting with underscore. So you need to get your data and then decorate it a bit in JS: https://salesforce.stackexchange.com/a/235214/799

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.