We have a dynamic component for tab
body, which defined as
<component :is="currentTab.itemType" :itemId="currentTab.itemId"></component>
Template has a span, which reflects itemId
- it changes every time when the currentTab
changed in tabs host component.
Each component of tab.itemType
has Vuex module, belongs to it specific type.
For example, there is store module product
with described state:
{
products: { [itemId: string]: IProduct }
}
When component created or itemId
changed, it tries to run load action and put loaded product to products
of vuex state.
So, there is Vue computed property, looks like
@State(productNamespace)
state: IProductState;
get currentProduct() {
return this.state.products[this.itemId];
}
or even
@Getter(GetterNames.GET_PRODUCT_BY_ID, bindingOptions)
getProductById: (itemId: string) => IProduct;
get currentProduct() {
return this.getProductById(this.itemId);
}
Each product has an attributes
list, which is iterated by v-for
with :key
.
<v-list :key="itemId"><!-- itemId has no effect there -->
<v-list-item v-for="attribute in currentProduct.attributes" :key="attribute.id">
...
</v-list-item>
</v-list>
The problem is:
when we change itemId
, the attributes list displays all attributes from last added product and does not refresh it when switching to previous "tabs" with another itemId
but the same itemType
.
I've tried to set :key
of parent div
as itemId
but with no effect.
When I set :key
to <component>
, vuex state becomes broken.
Vue version is 2.6.10
UPDATE:
It does not work with simple property of product too:
{{ currentProduct.name }}
Summary:
There is the itemId
property in. And computed property which depends on it. So computed property does not reflect changes when itemId
prop changed while Vuex collection does not changed.
Confirmed:
Computed property renews only when state.products collection changed. I've emulate this by run createProduct
action for each tab switching. Collection in vuex state accepts unwatched product stub and reflect changes to legal currentProduct
with given itemId
UPDATE 2: component with watcher. Still no way...
@Component
export default class Product extends Vue {
@Prop({ type: Object, required: true })
readonly tabItem: ITabItem;
@State(productNamespace)
state: IProductState;
itemId: string;
created() {
//...
this.initCurrentProduct();
}
// No changes until state.products was changed.
get currentProduct(): IProduct | {} {
if (!this.state) return {};
return this.state.products[this.itemId];
}
@Watch('tabItem')
onTabItemChanged()
{
DEBUG && console.log('Tab changed: keep moving!');
this.initCurrentProduct();
}
private async initCurrentProduct() {
const { isNew, itemId } = this.tabItem;
if (itemId === this.itemId)
return;
DEBUG && console.log('ItemId changed.');
this.itemId = itemId;
// ...
}
// ...
}
Okay so the property you're passing to the dynamic component is currentTab.itemId
which means itemId is actually an element in the currentTab
object not the root Vue data object?
Vue does not track nested objects by default, it will only trigger redraw when the entire object is changed (for example if you do something like currentTab = {...}
). You can either:
Use a watcher on currentTab
with deep: true
attribute: https://v2.vuejs.org/v2/api/#watch, and then trigger redraw with this.$forceUpdate
whenever it is called.
Move itemId
to the root of data and just update it from there