Search code examples
vuejs3nuxt3.jspinia

Pinia: How to dynamically add new enteries to store state


I have a use case with the pinia in vue 3 that I want to dynamically add new entries to the pinia store using the store actions. for example if I have a state called firstName and if I call a the action of the store it should add new state called lastName in the state as well. Here is what I have tried

import { defineStore } from "pinia";

export const useAdvanceListingsFilterStore = defineStore(
  "advance-listing-filters",
  {
    state: () => {
      return {
        firstName: "jhon",
      };
    },
    actions: {
      setLastName(payload) {
        return {
          ...this.state,
          lastName: payload,
        };
      },
     
     
    },
  }
);


The new state should include the fistName and lastName fields.


Solution

  • UPDATE

    Accessing user over $state was not necessary. It works well directly:

    this.user.lastName = payload;
    

    const { ref, createApp } = VueDemi
    const { createPinia, defineStore, storeToRefs } = Pinia
    
    const useAlertsStore = defineStore("advance-listing-filters",
      {
        state: () => ({ user: { firstName: "John" }}),
        actions: {
          setLastName(payload) {
            this.user.lastName = payload;
          }
        }
      }
    )
    
    const App = {
      setup() {
        const store = useAlertsStore()
        const { user } = storeToRefs(store)  
        const lastName = ref('Doe')
      return {
          store,
          user,
          lastName
        }
      }
    }
    
    const app = createApp(App)
    const pinia = createPinia()
    app.use(pinia)
    app.mount('#app')
    [v-cloak] {  display: none; }
    <div id="app" v-cloak>
      <label>Name: </label> <b>{{store.user.firstName}} {{store.user.lastName}}</b><br /><br />
      <label>First Name:</label> <input :value="user.firstName" disabled /><br /><br />
      <label>Last Name:</label> <input v-model="lastName" /><br /><br />
      <button @click="store.setLastName(lastName)">Set Last Name</button>&nbsp;
      <button @click="store.$reset()">Reset</button>
    </div>
    <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
    <script src="https://unpkg.com/[email protected]/lib/index.iife.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/pinia.iife.js"></script>


    The simplest way would by add lastName: null to your state, but I guess it is not what you are trying to achieve.

    I have tried to add new items to the state internally using state.$path (see Mutating the state), but the new lastName item was still not accessible outside the state using state.lastName. So I haven't found a way to achieve your goal directly. But there is another way to do it.

    You can use a nested object to achieve your goal.

    See the playground below.

    I should also state, that adding state items dynamically makes your application less predictable.
    So, I would rather rethink the design and flow of data.

    const { ref, createApp, defineComponent } = Vue
    const { createPinia, defineStore, storeToRefs } = Pinia
    
    const useAlertsStore = defineStore("advance-listing-filters",
      {
        state: () => {
          return {
            user: { firstName: "John" }
          };
        },
        actions: {
          setLastName(payload) {
            this.$state.user.lastName = payload;
          }
        }
      }
    )
    
    const App = {
      setup() {
        const store = useAlertsStore()
        const { user } = storeToRefs(store)  
        const lastName = ref('Doe')
      return {
          store,
          user,
          lastName
        }
      }
    }
    
    const app = createApp(App)
    const pinia = createPinia()
    app.use(pinia)
    app.mount('#app')
    <div id="app">
    <label>Name: </label> <b>{{store.user.firstName}} {{store.user.lastName}}</b><br /><br />
    <label>First Name:</label> <input :value="user.firstName" disabled /><br /><br />
    <label>Last Name:</label> <input v-model="lastName" /><br /><br />
    <button @click="store.setLastName(lastName)">Set Last Name</button>
    </div>
    <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
    <script src="https://unpkg.com/[email protected]/lib/index.iife.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/pinia.iife.js"></script>