Search code examples
javascriptvue.jsvuejs3pinia

How to use pinia store getters on v-model of an input?


In my application, i am working on storing user form responses. I have created pinia store and created each forms object. Using this store into my component and passing the getter function of that store into variable,

That variable, i am using as a v-model. After giving input also it is not returning anything. As well as i am not getting any error for this. Did i follow any wrong approach?

Userstore.js

    import { defineStore } from 'pinia';
    
    export const userResponses = {
      formsResponses: {
        form1: {
          input1: '',
        },
      },
    };
    
    export default defineStore('userStore', {
      state() { return userResponses; },
    
      getters: {
        getFormsInput: (state) => state.formsResponses.form1,
      },
      actions: {
        setFormsResponses(formResponses) {
          this.formsResponses.form1 = formResponses;
        },
      },
    });

Form1.vue

  <template>
    <input type="text" name="input_form" v-model="input"/>
    <button type="Primary" @click="submit">submit</button>
  </template>
    
    <script setup>
    import { computed, ref } from 'vue';
    import useUserStore from '@/store/userStore';
    
    
    const userStore = useUserStore();
    
    const input = ref('');
    const responses = computed(() => userStore.getFormsInput);
    
    reponses.value.input1 = input.value;
    
    function submit() {
      console.log(reponses.value.input1); // it is giving nothing
    }
    
    </script>

Why i am not able to use getters or not updating the value? Shall i directly can use getters into v-model?


Solution

  • There are many ways to dual-bind a store's state to a form input (writable computed directly on store's state, storeToRefs, toRefs, or a writable computed with store getter and action, to name a few).

    The shortest is:

    const { someState } = toRefs(useStore())
    

    Now you can use someState with v-model and it will update the store state directly.
    To rename the store state members and avoid name clashes:

    const { 
      foo: localFoo,
      bamboo: localBamboo
    } = toRefs(useStore())
    // console.log(localBamboo.value)
    

    All of the above create ref()s. If you use them in the script, you need to use .value, unlike in <template>, where they are unwrapped by Vue.


    If you want complete control over the read and write methods (for validation or transforming the data on the fly), use a Writable Computed (a.k.a: computed getter/setter):

    const store = useStore()
    const myInput = computed({
      get() {
        return store.someState
      },
      set(val) {
        store.someState = val
      }
    })
    

    You don't need it here but you'll find it's the bread and butter of complex forms, especially when you want granular control and custom validation.

    The above is very close to how writing to store state is done using Options API:

      computed: {
        myInput: {
          get() { 
            return useStore().someState
          },
          set(val) { 
            useStore().someState = val
          }
        }
      }
    

    Notably, pinia gives you an alternative to toRefs, called storeToRefs which provides all store state and getters as reactive refs. I have no idea if it's more than just a re-export of toRefs but, since it's coming from 'pinia', I trust it and generally prefer it over toRefs (although I haven't heard of a case where toRefs works differently):

    <script setup>
    import { useUserStore } from "../store";
    import { storeToRefs } from "pinia";
    
    const { form1 } = storeToRefs(useUserStore());
    </script>
    <template>
      <input v-model="form1.input1" />
    </template>
    

    See it working. The input basically writes directly to store's state.

    If you want to link a store getter to a v-model it must have a setter (as shown above).

    Note: I significantly reduced your store's structure. Try not to over-complicate things without a good reason.


    If you want a local form state (and the user should pick when to save/reset):

    Local form state example.
    Again, removed unnecessary fluff/boilerplate.
    Note the destructuring used at the two touching points between store's state and the local state. You'd think the submit assignment could be:

    form1.value = localForm
    

    ..., but, once you've done that, localForm's reactivity is attached to the store state and every user input is saved on store, without submit().


    Related links:


    1 - need to use Setup stores syntax to have "writable getters".