Lightning Web Components – Mutating API Property to Contain Key to Be Iterable in for:each

lightning-web-components

How can I pass an array of strings (errors) into a separate child component (c-component-errors), and be able to show a toast for each one by iterating them using <template for:each…

My issue is that <template for:each requires the first child to have a key attribute with a unique identifier. How do I add this key to the data structure of each error, now that the initial data structure is just an array of strings?

A workaround could of course be to prepare the data structure before updating/setting the "errors" property, but I want to have the componentErrors component to be easily reusable.

Parent.html

<c-component-errors errors={errors}></c-component-errors>

Parent.js

@track errors;
...
.catch(error => {
   this.errors.push(...reduceErrors(error)); //using reduceErrors from the ldsUtils here
});

componentErrors.html

<template for:each{errors} for:item="error">
   <div key={error.key}>{error.message}</div>
</template>

componentErrors.js

@api errors;

Best Answer

You're passing an array of string to componentErrors, but in its for:each you're requiring an array of elements with key and message properites, so it should not work.
Anyway if you have an array, you always have an unique key for each element: its index.
So you could map your original array of string into an array of element with key and message. You don't need to do it before passing the array to the child component, you can do it in the child component itself providing a getter and a setter.

_errors;

@api
set errors(errorList) {
    this._errors = errorList.map((message, key) => {
        return {
            key,
            message
        };
    });
}

get errors() {
    return this._errors;
}

By the way, there is a typo in componentErrors.html, there is a missing = after the for:each

<template for:each={errors} for:item="error">
   <div key={error.key}>{error.message}</div>
</template>

Edit: If you setted errors on the child component only when it's added to the dom

<c-component-errors errors={errors}></c-component-errors>

It won't get automatically updated when parent's errors changes. You have to explicit set it:

...
.catch(error => {
   this.errors.push(...reduceErrors(error)); //using reduceErrors from the ldsUtils here
   // You should either explicitly set the property on child
   const componentErrors = this.template.querySelector('c-component-errors');
   componentErrors.errors = this.errors;
   // Or change the reference of the parent's property
   // this.errors = [...this.errors];
});
Related Topic