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

4

9

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?

Orlosky answered 31/5, 2023 at 6:30 Comment(2)
In what you posted it is not clear what you want to achieve: ① you want a local state in the form component which is only saved to the pinia store when a Submit button is pressed (so the user can have a Reset button to reset the form to its store state) or ② you want the form to be connected to the store and all user inputs should be automatically saved in store. Which one is it?Candelariacandelario
The 2nd option. I want to store all user responses into the storeOrlosky
C
20

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".

Candelariacandelario answered 1/6, 2023 at 9:25 Comment(0)
P
2

Because no one else here posted the Vue 3 Options API version, here it is:

import { defineStore } from 'pinia';

export const kittenStore = defineStore('kitten', {
  state: function () {
    return {
      kittenName: ''
    };
  },
  actions: {
    setKittenName: function (name) {
      this.kittenName = name;
    }
  }
});
<template>
  <input v-model="kittenName">
</template>

<script>
import { kittenStore } from '@/stores/kitten.js';

export default {
  name: 'ExampleComponent',
  computed: {
    kittenName: {
      get: function () {
        return kittenStore().kittenName;
      },
      set: function (value) {
        kittenStore().setKittenName(value);
      }
    }
  }
};
</script>

The great thing about this approach is that every file looks exactly the same. There is code organization enforced at the framework level.

Patently answered 14/1 at 1:35 Comment(1)
Options API hasn't changed since Vue2, which means "Vue3 Options API" is not a thing (and there are plenty of Vue2 examples on how to do this in Options API). As a side-note, the action is useless in your store. You can do kittenStore().kittenName = value in the setter.Candelariacandelario
A
0

I believe you made a typo mistake here.

const responses = computed(() => userStore.getFormsInput);
    
responses.value.input1 = input.value; //typo responses
    
function submit() {
  console.log(responses.value.input1); // typo responses
}
Africander answered 1/6, 2023 at 2:29 Comment(0)
A
-1

Getter from pinia is a computed value of a state afterall. You should prevent using a computed value as a v-model. Especially when there is no getter and setter for getter in pinia right now.

If the getFormsInput is only for returning a specific property of a json. I suggest you to mutate directly the state by accessing formsResponses.form1.input1.

<template>
  <input type="text" name="input_form" v-model="userStore.formsResponses.form1.input1"/>
  <button type="Primary" @click="submit">submit</button>
</template>

<script setup>
import { computed, ref } from 'vue';
import useUserStore from '@/store/userStore';

const userStore = useUserStore();

function submit() {
  console.log(userStore.formsResponses.form1.input1); //they should be the same
  console.log(userStore.getFormsInput); //they should be the same
}

</script>

Btw I don't think .value is needed when you try to access the value of a json property.

Africander answered 31/5, 2023 at 6:56 Comment(2)
Now the state value is not updating. I have made the variable reactive by using computed.Orlosky
I've updated the answer. There were typo and wrong variable name.Africander

© 2022 - 2024 — McMap. All rights reserved.