Search code examples
apexvisualforcesalesforce-lightninglwc

LWC: Sibling component not rerendering after Parent is updated


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.


Solution

  • (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