2

Here's an example of a component:

<script>
    export default {
        name: 'my-form',

        computed: {
            myModules() {
                return this.$store.state.myModules;
            }
        }
</script>

<template>
    <form>
        <p v-for="module in myModules">
            <input type="checkbox" :value="module.id" />
            <label>module.name</label>
        </p>
        <button type="submit">Submit</button>
    </form>
</template>

The associated store:

    state: {
        myModules: []
    },

    mutations: {
        setModules(state, modules) {
            state.myModules = modules;
        }
    },

    actions: {
        getModules({commit}) {
            return axios.get('modules')
            .then((response) => {
                commit('setModules', response.data.modules);
            });
        }
    }

And finally, an example of return of the API "getModules":

modules : [
    {
        id: 1,
        name: 'Module 1',
        isActive: false
    },
    {
        id: 2,
        name: 'Module 2',
        isActive: false
    },
    {
        id: 3,
        name: 'Module 3',
        isActive: false
    }
]

My question: what's the best way to change the "isActive" property of each module to "true" when I check the checkbox corresponding to the associated module, directly in the store?

I know that Vuex's documentation recommends to use "Two-way Computed Property" to manage the forms, but here I don't know the number of modules that the API can potentially return, and I don't know their name.

Thank you in advance!

2 Answers 2

1

This is a little bit wicked approach, but it works. You can create an accessor object for every item you access in a loop:

const store = new Vuex.Store({
  mutations: {
    setActive (state, {index, value}) {
      state.modules[index].isActive = value
    }
  },
  state: {
    modules : [
      {
        id: 1,
        name: 'Module 1',
        isActive: false
      },
      {
        id: 2,
        name: 'Module 2',
        isActive: false
      },
      {
        id: 3,
        name: 'Module 3',
        isActive: false
      }
    ]
  }
});
const app = new Vue({
  el: '#target',
  store,
  methods: {
    model (id) {
      const store = this.$store;
      // here i return an object with value property that is bound to 
      // specific module and - thanks to Vue - retains reactivity
      return Object.defineProperty({}, 'value', {
        get () {
          return store.state.modules[id].isActive
        },
        set (value) {
          store.commit('setActive', {index: id, value});
        }
      });
    }
  }
})
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuex/dist/vuex.min.js"></script>
<div id="target">
  <div v-for="(item, id) in $store.state.modules">
    Module #{{ item.id }} state: {{ item.isActive }}
  </div>
  <div v-for="(item, id) in $store.state.modules">
    <label>
      Module #{{ item.id }}
      <input type="checkbox" v-model="model(id).value"/>
    </label>
  </div>
</div>

This is still quite a messy approach, but at least you don't have to commit mutations directly in template. With a little help of Vue.set() you can use this approach even to overcome standard reactivity caveats.

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

2 Comments

I don't really know if it's a good practice, but it works perfectly! Thank you.
If you're interested, take a look at my solutions. I find them a bit more organized.
1

I have an alternative solution for you. You could make a child component for the checkboxes to clean up the code a bit.

UPD: I just realised that everything that I and @etki proposed is an overkill. I left the old version of my code below in case you still want to take a look. Here is a new one:

const modules = [{
    id: 1,
    name: 'Module 1',
    isActive: true,
  },
  {
    id: 2,
    name: 'Module 2',
    isActive: false,
  },
  {
    id: 3,
    name: 'Module 3',
    isActive: false,
  },
];

const store = new Vuex.Store({
  state: {
    myModules: [],
  },
  mutations: {
    SET_MODULES(state, modules) {
      state.myModules = modules;
    },
    TOGGLE_MODULE(state, id) {
      state.myModules.some((el) => {
        if (el.id === id) {
          el.isActive = !el.isActive;
          return true;
        }
      })
    }
  },
  actions: {
    getModules({
      commit
    }) {
      return new Promise((fulfill) => {
        setTimeout(() => {
          commit('SET_MODULES', modules);
          fulfill(modules);
        }, 500)
      });
    }
  }
});

const app = new Vue({
  el: "#app",
  store,
  data: {},
  methods: {
    toggle(id) {
      console.log(id);
      this.$store.commit('TOGGLE_MODULE', id);
    }
  },
  computed: {
    myModules() {
      return this.$store.state.myModules;
    },
    output() {
      return JSON.stringify(this.myModules, null, 2);
    },
  },
  mounted() {
    this.$store.dispatch('getModules').then(() => console.log(this.myModules));
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.js"></script>
<script src="https://unpkg.com/vue"></script>

<div id="app">
  <form>
    <div v-for="data in myModules">
      <label :for="data.id">{{ data.name }}: {{data.isActive}}</label>
      <input type="checkbox" :id="data.id" :name="'checkbox-' + data.id" :checked="data.isActive" @change="toggle(data.id)">
    </div>
  </form>
  <h3>Vuex state:</h3>
  <pre v-text="output"></pre>
</div>

As you can see above you could just call a function on input change and pass an id as a parameter to a method that fires vuex action.

The old version of my code.

A new one on jsfiddle

2 Comments

It's a good solution too, thank you! Etki's solution keeps the "v-model" system, and yours is less verbose. But both work.
@Baptiste v-model's still used in my first version based on child components though

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.