2
votes

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 attributes list, wich 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 wich 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;

    // ...
  }

  // ...
}

2
Shouldn't you be accessing this.$store.state... instead of this.$state...?Eggon
Sorry, my bad - I use 'vuex-class', so there is incorrect simplification. Just a minute, I correct it.Arsync
As I remember Vuex recommends getters for computed properties, instead of direct access to Store. Have you tried using a getter?Eggon
Yes, original takes it from functional getter. It seems like only last item added reflect its properties, but switching back has no effect on computed properties with changed itemId. So I think there is products collection changed event raised.Arsync
I'm not sure if we're on the same page here. Your'e saying that there's a getter but in the code shown you just access the store directly without any getter. Also the computed property is get currentProduct() - why space? Is it in computed section?Eggon

2 Answers

1
votes

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:

  1. Use a watcher on currentTab with deep: true attribute: https://vuejs.org/v2/api/#watch, and then trigger redraw with this.$forceUpdate whenever it is called.

  2. Move itemId to the root of data and just update it from there

0
votes

in your vuex mutation

let items = [...state.items]; // create a new copy

// mutate it 
items.map(item => item.selected = true);

// return the new copy
state.items = items;