0

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;
5
  • My issue is that <template for:each requires the first child to have a key attribute with a unique identifier. Is this a functional or design requirement for you?
    – arut
    Jul 4, 2022 at 14:48
  • we are iterating errors. We are not sure whether the error has a key with Key or not. Can you try <div key={error}>{error.message}</div> Jul 4, 2022 at 14:54
  • Also, why are you doing this <div key={error.key}>{error.message}</div> when you want to display toast?
    – arut
    Jul 4, 2022 at 15:01
  • 1
    @arut it's a design requirement. I could easily make this work by making sure the data I pass to the component has both a key and a message.
    – Andreas86
    Jul 4, 2022 at 18:38
  • & do you already have requirement/ logic for unique key generation at your end?
    – arut
    Jul 5, 2022 at 3:45

1 Answer 1

0

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];
});
7
  • I should have made it more clear, that this was not a real example. It was only to allow for a discussion around how to pass a string array to the component, and then have it mutate that into an array of a complex type with both message and key in it.
    – Andreas86
    Jul 4, 2022 at 18:40
  • I tried creating a setter as you suggest, but it won't get called once the value of the string array changes from the parent. How do I make sure that it does?
    – Andreas86
    Jul 4, 2022 at 18:42
  • 1
    @Andreas86 .map() creates a new array, so every time the original ones changes, the parent must pass it to the child component. Here Salesforce suggest to send only primitives (not your case), or to copy data to a new object before passing it between components, that's what map() does. "Copying the data to a new object ensures that you’re sending only the data you want, and that the receiver can’t mutate your data."
    – RubenDG
    Jul 4, 2022 at 18:53
  • Hmm.. are you suggesting creating a new object on the parent side or the child side? because I don't see the child setter ever getting called when the parent changes its errors property
    – Andreas86
    Jul 4, 2022 at 20:43
  • 1
    @Andreas86 Since error is not a primitive, you should either explicitly set it on child component or change its reference, i.e. this.errors = [...this.errors];. I've added this point to the answer
    – RubenDG
    Jul 5, 2022 at 7:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .