Search code examples
javascriptsalesforcelwc

Validate multiple required LWC's on same flow screen, WITHOUT losing values


So I've recently started jumping into LWC's more seriously, and I'm trying to figure it all out. I've created a custom picklist flow component which accepts a collection of strings as options (why this isn't standard I'll never know). I've managed to prevent moving onto the next page if the component is marked as "Required" using @api validate().

However, the issue is that if multiple of these components are on the same screen and a single one fails validation; then all of the components (even the valid ones) have their values cleared after showing a very brief loading icon. I'd like to mimic the standard required screen component/field functionality as close as possible to avoid input frustrations and inconsistencies.

Below is my current code:

component.js

import { LightningElement, api, track } from 'lwc';

export default class StringPicklistFlowComponent extends LightningElement {
    @api optionsArr;
    @api label;
    @api isRequired = false;
    @api value;

    get isNotRequired(){return !this.isRequired;}

    get options(){
        var arr = [];
        for(var i = 0; i < this.optionsArr.length; i++){
            var tmp = this.optionsArr[i].split(/,\s*/g);
            arr.push({label: tmp[0], value: tmp[1]});
        }
        return arr;
    }

    handleSelect(evt){
        this.value = evt.detail.value;
    }

    @api validate(){
        return {
            isValid: this.isNotRequired || this.value != null,
            errorMessage: "Please select an option"
        }
    }
}

component.html

<template>
    <label for="customPicklist" class="slds-form-element__label slds-rich-text-editor__output">
        <span class="slds-required" hidden={isNotRequired}>*</span>
        {label}
    </label>
    <lightning-combobox id="customPicklist" value={value} options={options} onchange={handleSelect}>
    </lightning-combobox>
</template>

Note that I did create a label manually. This is because the default functionality when the field was directly marked as "required" would constantly show a field error while the component value was null, which is a bit annoying.

Any help on this would be awesome, as this has been nagging at me for the better part of a day.


Solution

  • I managed to find a solution, so I'll post it here in case anybody else comes across this issue. Basically, the problem boils down to the component getting re-rendered, and hence losing it's previous data. To get around this, you can use the native browser sessionStorage to store values, and then when re-rendered use the connectedCallback hook to set the component value. Here's what my code looks like now:

    component.js

    import { LightningElement, api, track } from 'lwc';
    
    export default class StringPicklistFlowComponent extends LightningElement {
        @api isRequired = false;
        @api optionsArr;
        @api label;
        @api value;
    
        @api connectedCallback(){
            if(!!sessionStorage[this.storageTag])
                this.value = sessionStorage[this.storageTag];
            else
                sessionStorage[this.storageTag] = this.value;
        }
    
        get storageTag(){ return this.label + 'myTag5135'; }
    
        get isNotRequired(){ return !this.isRequired; }
    
        get options(){
            var arr = [];
            for(var i = 0; i < this.optionsArr.length; i++){
                var tmp = this.optionsArr[i].split(/,\s*/g);
                arr.push({label: tmp[0], value: tmp[1]});
            }
            return arr;
        }
    
        handleSelect(evt){
            this.value = evt.detail.value;
            sessionStorage[this.storageTag] = this.value;
        }
    
        @api validate(){
            return {
                isValid: this.isNotRequired || !!this.value,
                errorMessage: "Please select an option"
            };
        }
    }
    

    component.html

    <template>
        <div>
            <label for="customPicklist" class="slds-form-element__label">
                <abbr class="slds-required" hidden={isNotRequired}>* </abbr>{label}
            </label>
            <lightning-combobox dropdown-alignment="auto" id="customPicklist" data-fieldname={label} variant="label-hidden" value={value} options={options} onchange={handleSelect}>
            </lightning-combobox>
        </div>
    </template>
    

    If you'll notice, I AM using the label property which isn't ideal (no enforcement that it's unique). I'd like to use the components api name in the flow so I can virtually guarantee uniqueness, however I've yet to find how to get this default property in the component. If anybody knows how, I'd really appreciate it. Anyways, I hope this helps anyone whose had similar frustrations with this wierd default limitation.