Vinee
Vinee

Reputation: 1654

Stencil component not rendering the updated tabs

import { Component, h, State } from '@stencil/core';
// import '@webcomponents/custom-elements';
import '@clr/core/icon/register';
import { ClarityIcons, plusIcon } from '@clr/core/icon';

ClarityIcons.addIcons(plusIcon);

@Component({
  tag: 'tabs-component',
  styleUrl: 'tabs-component.css',
  shadow: false,
})
export class TabsComponent {
  @State() tabs: Array<object> = [
    (
      <li role="presentation" class="nav-item">
        <button id="tab3" class="btn btn-link nav-link" aria-controls="panel3"
          aria-selected="false" type="button">Cloud</button>
      </li>
    )
  ];

  addTab(onHead = true) {
    // debugger
    const tab = (
      <li role="presentation" class="nav-item">
        <button id="tab3" class="btn btn-link nav-link" aria-controls="panel3"
          aria-selected="false" type="button">Dashboard</button>
      </li>
    );
    if (onHead) {
      this.tabs.unshift(tab);
    } else {
      this.tabs.push(tab);
    }
    console.log(this.tabs);
  }

  render() {
    return (
      <div>
        <ul id="demoTabs" class="nav" role="tablist">
          <li role="presentation" class="nav-item" onClick={() => this.addTab()}>
            <cds-icon shape="plus" class="cursor"></cds-icon>
          </li>
          {this.tabs}
        </ul>
        <section id="panel1" role="tabpanel" aria-labelledby="tab1">
          tab1
        </section>
        <section id="panel2" role="tabpanel" aria-labelledby="tab2" aria-hidden="true">
          tab2
        </section>
        <section id="panel3" role="tabpanel" aria-labelledby="tab3" aria-hidden="true">
          tab3
        </section>
      </div>
    );
  }

}

Upvotes: 0

Views: 2826

Answers (3)

Simon H&#228;nisch
Simon H&#228;nisch

Reputation: 4968

This is a matter of referential equality. Objects are always passed by reference not by value and therefore two objects are never equal (reference-wise, even if they contain the same values).

The array is a special kind of object and therefore is also passed by reference. Modifying an array's value does not change its reference.

Some examples to illustrate this:

const foo = ['a', 'b'];
console.log(foo === ['a', 'b', 'c']); // false

foo.push('c');
console.log(foo === ['a', 'b', 'c']); // still false

console.log(['a', 'b', 'c'] === ['a', 'b', 'c']); // actually always false

console.log(foo === foo); // always true because it is the same reference

Stencil compares @State() decorated class members using the same strict equality operator === (same goes for @Prop()). If the value is the same, then the component is not re-rendered.

In the case of your tabs state, the value of this.tabs is a reference to the array that you assign to it. Modifying the array (e. g. this.tabs.push(...)) only changes the value of the array referenced by this.tabs but not the actual reference that is stored in this.tabs.

Therefore you need to re-assign this.tabs in order to let Stencil know that this member has changed. The easiest way to do that is

this.tabs = [...this.tabs];

which spreads the values of the array into a new array (which returns a new reference). Alternatively something like this.tabs = this.tabs.slice() would also do the trick (anything that returns a new array works).

In your case it's easiest to change your addTab method to

addTab(onHead = true) {
  const tab = (
    <li role="presentation" class="nav-item">
      <button id="tab3" class="btn btn-link nav-link" aria-controls="panel3"
        aria-selected="false" type="button">Dashboard</button>
    </li>
  );

  this.tabs = onHead ? [tab, ...this.tabs] : [...this.tabs, tab];
}

(i. e. either spread the original value before or after the new item).

Upvotes: 2

Thomas
Thomas

Reputation: 8859

Stencil performs strict equality checks (===) to determine whether a Prop/State variable has changed which is why it doesn't detect push and unshift as changes. You have to make sure to replace the array with a new one. The quickest way to do this in your example is to manually replace the array with a copy after the manipulation:

    if (onHead) {
      this.tabs.unshift(tab);
    } else {
      this.tabs.push(tab);
    }
    this.tabs = [...this.tabs];

See the Stencil docs for updating arrays.

Upvotes: 1

Vinee
Vinee

Reputation: 1654

Look like temp variable works the trick, strange.

    const tempTabs = [...this.tabs];
    if (onHead) {
      tempTabs.unshift(tab);
    } else {
      tempTabs.push(tab);
    }
    this.tabs = tempTabs;

Upvotes: 0

Related Questions