Reputation: 1654
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
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
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
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