How to update multiple Vue 3 components when Pinia array changes
Asked Answered
S

1

6

I can't get multiple components accessing the same store respond to updates, until I mess with a dom element to trigger new render.

In my Pinia store, I have an array, and an update method:

    let MyArray: IMyItem[] = [
    { id: 1,
      name: "First Item",
    }, ....
    let SelectedItem = MyArray[0]
    const AddItem = ( n: string, ) => {       
        MyArray.push({ id: createId(), name: n, });
        };

    return { MyArray, SelectedItem, AddItem  }

In one Vue component, I have text inputs and a button to call the store's method:

    function handle() {store.AddItem(name.value));

In another Vue component, on the same parent, I use a for loop to display allow for selecting an item:

    <div v-for="item in store.MyArray">
          <input type="radio"...

No changes with these efforts:

    const { MyArray } = storeToRefs(store);
    const myArray = reactive(store.MyArray);
    // also watching from both components...
    watch(store.MyArray, (n, o) => console.dir(n));
    // also... lots of other stuff.
    const myArray = reactive(store.MyArray);
    watch(myArray, (n, o) => console.dir(n));

I also experimented with <form @submit.prevent="handle"> triggering nextTick by adding a string return to the store's method.

I assume the reason clicking around makes it work is because I'm changing the the store's SelectedItem, and its reactivity calls for re-rendering, as it is v-model for a label.

The docs say Array.push should be doing it's job... it just isn't bound the same way when used in v-for.

What's needed to trigger the dom update? Thanks! πŸ’©

Sisera answered 29/4, 2022 at 16:29 Comment(4)
In the component that should update, define a computed property that just returns the store's value and use the computed property for rendering your template. – Bazan
could you show ur pinia store – Fortify
The top code block is the store. It exposes 3 things: MyArray, SelectedItem, and AddItem function. – Sisera
let MyArray: IMyItem[] = [ - it's not reactive, this is a mistake. const myArray = reactive(store.MyArray) - doesn't make sense because store.MyArray is already supposed to be reactive at this point – Straightjacket
S
9

As comments pointed out, the main issue is your store state is not declared with the Reactivity API, so state changes would not trigger watchers and would not cause a re-render.

The solution is to declare MyArray as a reactive and SelectedItem as a ref:

// store.js
import { defineStore } from 'pinia'
import type { IMyItem } from './types'
import { createId } from './utils'
         πŸ‘‡      πŸ‘‡
import { ref, reactive } from 'vue'

export const useItemStore = defineStore('item', () => {
                   πŸ‘‡
  let MyArray = reactive([{ id: createId(), name: 'First Item' }] as IMyItem[])
                     πŸ‘‡
  let SelectedItem = ref(MyArray[0])
  const AddItem = (n: string) => {
    MyArray.push({ id: createId(), name: n })
  }

  return { MyArray, SelectedItem, AddItem }
})

If using storeToRefs(), make sure to set the ref's .value property when updating the SelectedItem:

// MyComponent.vue
const store = useItemStore()
const { SelectedItem, MyArray } = storeToRefs(store)
const selectItem = (id) => {
                 πŸ‘‡
  SelectedItem.value = MyArray.value.find((item) => item.id === id)
}

But in this case, it's simpler to use the props off the store directly:

// MyComponent.vue
const store = useItemStore()
const selectItem = (id) => {
  store.SelectedItem = store.MyArray.find((item) => item.id === id)
}

demo

Sassaby answered 29/4, 2022 at 22:11 Comment(0)

© 2022 - 2024 β€” McMap. All rights reserved.