2

I have the following setup in Vue 3.

A vuex store with an array as part of state:

const store = createStore({
  state: {
    questions: [
      { text: 'A', value: false },
      { text: 'B', value: false },
      { text: 'C', value: true },
    ],
  },
  mutations: {
    updateQuestionValue(state, { index, value }) {
      state.questions[index].value = value;
    },
  },
});

And a component which attempts to render a list of checkboxes that should correspond to the "questions" array in state.

<template>
    <div v-for="(question, index) in questions">
        <label :for="'q'+index">{{question.text}}</label>
        <input :id="'q'+index" v-model="questionComputeds[index]" type="checkbox" />
    </div>
</template>

<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';

const store = useStore();

const questions = computed(() => store.state.questions);

const questionComputeds = store.state.questions.map((q, i) =>
    computed({
        get() {
             return store.state.questions[i].value;
        },
        set(value) {
             store.commit('updateQuestionValue', { index: i, value });
        },
    })
);
</script>

As you can see I am hoping to use v-model to enact two way binding for each input element in the list, but because I am using vuex with an array, I want to use the get/set option on my computed properties and access the specific computed with an index in the template. However I am finding this does not work. It doesn't throw an error, but it also doesn't work to bind the value of the checkbox to the .value prop in my question objects. Am I completely offbase about the tactic here? Can you even make arrays of "computed"s, like I am with .map()? Any way to use v-model with this data structure?

0

1 Answer 1

2

first problem:

In the first line of the template

<div v-for="(question, index) in questions">

questions is not defined. It could be replaced by

<div v-for="(question, index) in questionComputeds">

or the questions passed to the store on creation could be extracted to a variable, exported and imported in your component

second problem:

Your second line is missing a .value

Should be

<input v-model="questionComputeds[index].value" type="checkbox" />

These two changes should fix it. Considering that you don't need the index in the v-for either you get the

complete solution:
<template>
  <div v-for="question in questionComputeds">
    <input v-model="question.value" type="checkbox" />
  </div>
</template>

<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';

const store = useStore();

const questionComputeds = store.state.questions.map((q, i) =>
    computed({
      get: () => store.state.questions[i].value,
      set: (value) => { store.commit('updateQuestionValue', { index: i, value }) },
    })
);
</script>

Alternatives:

simple solution:
<template>
  <div v-for="(question, index) in questionComputeds">
    <input :checked="questionComputeds[index]" type="checkbox" @input="handleChange(index, $event)" />
  </div>
</template>

<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';

const store = useStore();

const handleChange = (i, e) => {
  store.commit('updateQuestionValue', { index: i, value: e.target.checked });
  // to verify:
  // console.log({ ...store.state.questions[i] });
}

const questionComputeds = computed(() => store.state.questions.map(q => q.value))

</script>
more complex (but more reusable) solution:

Extract your array of checkboxes to a separate component and define a custom v-model

Sign up to request clarification or add additional context in comments.

5 Comments

I am used to .value being used in the <script> section of a SFC but not in the template, can you speak a little more to why it is needed in the template in this case?
Also, sorry about the omission of the value of questions, that was just an oversight on my part. In my actual (non minimum reproducible example) code it was included as its own computed off of the vuex store and was necessary because I used it to populate a <label> element to go with each of the <inputs>
To answer your question about the .value. I think the best way to understand that is to rename (just to try can undo it of course) your store entries' props to be e.g. { text: 'A', booleanValue: false }. Then you will see that you have to change v-model="question.value" to v-model="question.booleanValue" and get: () => store.state.questions[i].value to get: () => store.state.questions[i].booleanValue. That shows that .value does not relate to Vue's "reactivity through proxy" implementation. It's the name of the prop key you have given to your questions.
And it would be nice if you could not only upvote but also accept the answer, since it is more than complete imho ;-)
Your response makes complete sense thanks for addressing that

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.