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>
);
}
}
Stencil component not rendering the updated tabs
2.5k views Asked by Vinee AtThere are 3 answers
On
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).
Stencil performs strict equality checks (
===) to determine whether a Prop/State variable has changed which is why it doesn't detectpushandunshiftas 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:See the Stencil docs for updating arrays.