Mandalina
Mandalina

Reputation: 446

Array of Objects Prop Decorator StencilJS

I want to pass an array of objects and then iterate through the array and render each object.

I have the following my html file:

<stencil-component 
   cards = '[{"cardTitle"="placeholder", "copy"="copy1"},{"cardTitle"="placeholder2", "copy"="copy3"}]'>
</stencil-component>

And in my tsx file I have the following:

@Prop() cards: Array<object> = [];

render(){
    return  (
      {this.cards.map(card) => {

        return (
          <div>
            <h1>{card.cardTitle}</h1>
            <p>{card.copy}</p>
          </div>
        );
      })}    
    )
}

I cant even get the array to display. I tried to use the JSON.parse function to parse the data and that didn't work either. If anyone could please help, I would really appreciate it!

Upvotes: 3

Views: 7864

Answers (2)

Christian Meyer
Christian Meyer

Reputation: 1781

Your Problem is that the JSON is actually no JSON (replace = with :) and further the data isn't parsed automatically to an object array when you share it through the attributes of the component. I would use a generic function like getDataFromProperty and outsource it in a different file that you can use it in all your web components. But lets keep it simple for now:

@Prop() cards: Array<object> = [];

private getDataFromProperty(toParse):any{
 try(JSON.parse(toParse)){
  return JSON.parse(toParse);
 }catch(e){
  //some different conversations you wanna try e.g.
   console.error('Couldnt JSON parse', e);
   if(toParse.split(',').length > 0) return toParse.split(',');
 }
}

render(){
 if(!this.cards) this.cards = this.getDataFromProperty(this.cards);
    return  (
      {this.cards.map(card) => {

        return (
          <div>
            <h1>{card.cardTitle}</h1>
            <p>{card.copy}</p>
          </div>
        );
      })}    
    )
}

<stencil-component 
   cards = '[{"cardTitle": "placeholder", "copy": "copy1"},{"cardTitle": "placeholder2", "copy": "copy3"}]'>
</stencil-component>

Try and Catch will prevent some hard errors and give you informations what happened if it couldn't be parsed. Maybe you have to tweak it a bit for your needs but the concept works. In the catch condition you can try some other techniques for data conversation.

Secondly i wanna share a complete different approach that depends on the environment you are using but you can use a public method. Than you can share data to the web component directly:

@Prop() cards: Array<object> = [];

@Method() setData(data){
  this.cards = data;
}

render(){
  //your render function as it was
}

Inside your HTML

    <stencil-component id="web-component"></stencil-component>
    <script>
     let data = '[{"cardTitle": "placeholder", "copy": "copy1"},{"cardTitle": "placeholder2", "copy": "copy3"}]';

     document.getElementById("web-component").setData(data);
     //just call the setData function from outside works with jquery too
    </script>

But there is one little thing you have to notice in that approach. Maybe your render function won't be called than you can do it manually in that function by just using this.render(); or you can use the @State decorator in front of your variable.

Upvotes: 1

Thomas
Thomas

Reputation: 8849

HTML attributes can only hold strings, so if you use the component outside of Stencil you will have to pass all properties as strings.

In Stencil (and probably some frameworks) you could pass it like this:

<stencil-component 
   cards={[{"cardTitle":"placeholder", "copy":"copy1"},{"cardTitle":"placeholder2", "copy":"copy3"}]}>
</stencil-component>

Note: Your JSON is incorrect, you need to replace = with :.

If you need to use the component in plain HTML you will have to either manually parse the property string or set it using javascript.

There are examples of this in the docs:

Setting the prop manually

import { Prop } from '@stencil/core';

export class TodoList {
  @Prop() myObject: object;
  @Prop() myArray: Array<string>;
}

HTML

<todo-list></todo-list>
<script>
  const todoListElement = document.querySelector('todo-list');
  todoListElement.myObject = {};
  todoListElement.myArray = [];
</script>

Watching props changes

import { Prop, State, Watch } from '@stencil/core';

export class TodoList {
  @Prop() myObject: string;
  @Prop() myArray: string;
  @State() myInnerObject: object;
  @State() myInnerArray: Array<string>;

  componentWillLoad() {
    this.parseMyObjectProp(this.myObject);
    this.parseMyArrayProp(this.myArray);
  }

  @Watch('myObject')
  parseMyObjectProp(newValue: string) {
    if (newValue) this.myInnerObject = JSON.parse(newValue);
  }

  @Watch('myArray')
  parseMyArrayProp(newValue: string) {
    if (newValue) this.myInnerArray = JSON.parse(newValue);
  }
}

HTML

<todo-list my-object="{}" my-array="[]"></todo-list>

Upvotes: 6

Related Questions