How to bind checkboxes to Vuex store?
Asked Answered
M

7

18

I have a component that contains some checkboxes. I need to be able to access which checkboxes are checked from other components in my Vue application, but I cannot for the life of me figure out (nor find online) how to properly connect the checkboxes to my Vuex store.

What is the right way to connect checkboxes within a component to a Vuex store, so that they act just as if the checkbox was connected to the components data via v-model?

Here is a starting point for what I'm trying to do (in a very very basic sense)

https://jsfiddle.net/9fpuctnL/

<div id="colour-selection">
  <colour-checkboxes></colour-checkboxes>
</div>

<template id="colour-checkboxes-template">
  <div class="colours">
    <label>
      <input type="checkbox" value="green" v-model="colours"> Green
    </label>
    <label>
      <input type="checkbox" value="red" v-model="colours"> Red
    </label>
    <label>
      <input type="checkbox" value="blue" v-model="colours"> Blue
    </label>
    <label>
      <input type="checkbox" value="purple" v-model="colours"> Purple
    </label>
    
    <chosen-colours></chosen-colours>    
  </div>
</template>

<template id="chosen-colours-template">
  <div class="selected-colours">
      {{ colours }}
    </div>
</template>

const store = new Vuex.Store({
  state: {
    colours: []
  }
});

Vue.component('colour-checkboxes', {
  template: "#colour-checkboxes-template",
  data: function() {
    return {
      colours: []
    }
  }
});

Vue.component('chosen-colours', {
    template: "#chosen-colours-template",
  computed: {
    colours() {
        return store.state.colours
    }
  }
});

const KeepTalkingSolver = new Vue({
  el: "#colour-selection"
});

The aim is to get the colours that are selected in the colour-checkboxes component to output in the chosen-colours component, going through the Vuex store.

Miner answered 9/3, 2017 at 3:55 Comment(1)
some code would help?Mateya
C
22

You can use computed property with getter as vuex getter and setter in computed property which will call a mutation for that state property to do this.

You can see an example of this here with two-way Computed Property:

<input v-model="message">
// ...
computed: {
  message: {
    get () {
      return this.$store.state.obj.message
    },
    set (value) {
      this.$store.commit('updateMessage', value)
    }
  }
}
Clothilde answered 9/3, 2017 at 5:7 Comment(2)
An example using a checkbox input field, like the question does, would be great here. Alle the examples online are with that text field that you give here as well.Leghorn
@KatinkaHesselink Little late, but I provided an answer with checkbox exampleKyser
K
13

I wanted to provide an answer that actually uses checkboxes.

There is one possible solution outlined here: Vuex dynamic checkboxes binding

And a simpler solution can be achieved something like the following:

<div v-for="tenant in tenants" 
     v-bind:key="tenant.id" 
     class="form-group form-check">

<input type="checkbox" 
     class="form-check-input" 
     v-bind:id="tenant.id" 
     v-bind:value="tenant.name" 
     @change="updateSelectedTenants">

Key here is calling a method using on-change, it will pass an event to the method with all the details needed to make the change.

The @change function:

updateSelectedTenants(e) {
  console.log('e', e.target)
  console.log('e', e.target.value)
  this.$store.dispatch('updateSelectedTenants', e.target)
}

Here I want the value, in this case will be the tenants name, but further inspection of the target also gives the 'id', and whether or not the checkbox is 'checked' or unchecked.

Over in the store, we can manipulate the 'selectedTenants' array:

updateSelectedTenants (context, tenant) {
  if(tenant.checked) {
    // Tenant checked, so we want to add this tenant to our list of 'selectedTenants'
    context.commit('addSelectedTenant', { id: tenant.id, name: tenant.value })
  } else {
    // otherwise, remove the tenant from our list
    context.commit('removeSelectedTenant', tenant.id)
  }
}

Here are the actual mutators:

addSelectedTenant (state, tenant) {
  this.state.selectedTenants.push(tenant)
},
removeSelectedTenant (state, id) {
  this.state.selectedTenants = this.state.selectedTenants.filter(tenant => {
    return tenant.id != id
  })

The vuejs docs are great, but sometimes they can be a little light on with real world examples. I don't think it's possible to achieve the above using a computed value, with get(), set()... but I'd like to see a solution that can.

Kyser answered 16/8, 2018 at 11:57 Comment(5)
this is waaaay too much code to do something that's built-in to vuejs. Instead use v-model='tenants' in the checkboxes (takes care of adding/deleting to the array of selected tenants), and mapstate the tenants (array) to vuex. Get rid of @change on the cb, and get rid of dispatch to vuex and get rid of mutators. Also, tenants array should not need both id and name, only id, name is superfluous.Gujarati
@LesNightingill Sounds good. Can you provide a working example? The question states they want the checkboxes bound to the Vuex store.. Agreed that this is not a problem, if storing in local state. Also, I can see now the "@change" could be moved to computed properties... but I'm not sure that would save much.Kyser
lol seriously @LesNightingill if you have such a nice solution please show your work ;)Competitive
Amazing Thank you!Metasomatism
This answer achieves the above using get() {} set() {} in a computed and utilizes v-modelEarmuff
G
8

OK I have been challenged to show my solution. Here it is on jsfiddle

the html is:

<div id="app">
  <label v-for="brother in ['Harpo','Groucho','Beppo']">
    <input type='checkbox' v-model='theBrothers' v-bind:value='brother' />
      {{ brother }}
  </label>
  <div>
  You have checked: {{ theBrothers }}
  </div>
</div> 

and the js is:

const store = new Vuex.Store({
  state: {
    theBrothers: []
  },
})

new Vue({
  el: "#app",
  store: store,
  computed: {
    theBrothers: {
      set(val){this.$store.state.theBrothers = val},
      get(){ return this.$store.state.theBrothers }
    }
 },
}) 
Gujarati answered 26/10, 2018 at 4:32 Comment(7)
Thanks Les. I think this.$store.state.theBrothers = val should be a Vuex mutation instead, but otherwise I think this works?Competitive
[vuex] do not mutate vuex store state outside mutation handlers.Perlman
You should never assign to the state directly. Use the mutations.Usa
I don't think this works. I've tried this answer but instead of modifying state directly, I used an action that commits a mutation like others have suggested. Its possible that I've done something wrong, but I believe that Vue is modifying the state directly and not dispatching the action in the setter. I added a console.log statement in the action, and it never logs. I also added a Logger to vuex, and it never writes to the console whenever I change the state through these checkboxes, but works for other components where I don't use checkboxes to update state. Just my 2 cents.Evidential
This does not work with checkboxes. I turned on strict mode for vuex, and I get errors stating to not mutate state directly (even with a setter that calls an action/mutation)Evidential
@Evidential the answer definitely works, take a look at the jsfiddle linked in the answer. If you tried something different, and your code is not working, then the problem is probably in your code.Gujarati
Sorry, I didn't word it correctly. It does work, but it does not follow vuex's pattern. When I tried your method, and enabled vuex's strict mode, it throws errors because the state was updated through this.$store.state instead of using an action or mutationEvidential
E
6

2021 - easy, readable, & taking advantage of the power of Vue/Vuex...

There are lots of complicated answers for a simple problem. Run the snippet below to see it in action.

Here is a working solution that solves all of the issues described below:

const store = new Vuex.Store({
  state: {
    names: ['Max'],
  },
  mutations: {
    setNames(state, names) {
      state.names = names;
    }
  }
});

new Vue({
  el: '#app',
  store,
  computed: {
    selectedNames: {
      get: function() {
        return this.$store.state.names;
      },
      set: function(val) {
      console.log(val);
        this.$store.commit('setNames', val);
      }
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vuex.js"></script>

<div id="app">
  <div>
    <input type="checkbox" v-model="selectedNames" :value="'John'" id="checkbox-1" />
    <label for="checkbox-1">Click me to add my value to the state</label>

    <br />

    <input type="checkbox" v-model="selectedNames" :value="'Max'" id="checkbox-2" />
    <label for="checkbox-2">I am preselected since my value already exists in the state <code>names</code> array</label>

    <div>State: <strong>{{ names }}</strong></div>
  </div>
</div>

Long story short all you need to do is take a piece of state (names below), create a mutation (setNames below) to set it, and then bind the v-model to a computed (selectedNames below) that has a getter and setter, the getter gets the piece of state names, and the setter calls the mutation setNames.

In my opinion this is the cleanest solution to this problem because it follows the natural pattern of Vue/Vuex and how checkboxes are typically implemented.

Other answers in here attempt to mutate the state directly without mutations, while some other answers avoid using a v-model which presents issues with having a preselected value and requires much more code, and finally the accepted answer doesn't even show any HTML template code on how to implement it.

Earmuff answered 29/7, 2021 at 12:36 Comment(2)
Let me see if I understand correctly: the v-model is a computed property, so when we bind the checkboxes to it, first the getter is called, returning an array. When we tick a box, is the original array modified, or a modified copy created? Either way, the setter is called with the modified array, and passes it to the mutation. Is that correct? (If so, should the name argument in the mutation be names?)Mears
@DavidMoles yes you're correct, I updated the naming in my snippet. Good catch, thanks!Earmuff
C
1

Use @change to update Vuex as needed:

HTML:

<input
  v-for='item in items'
  @change='update_checkboxes'
  v-model='selected_checkboxes'
  :value='item.id'
  type='checkbox
/>
<label>{{item.name}}</label>

JS:

data: function(){ 
  return {
    selected_checkboxes: [] // or set initial state from Vuex with a prop passed in
  }
},
methods: {
  update_checkboxes: function(){
    this.$store.commit('update_checkboxes', this.selected_checkboxes)
  }
}
Competitive answered 24/10, 2018 at 21:49 Comment(0)
R
0

Based on the solution from @Saurabh - it is important to use actions and getters rather than directly accessing vuex state - this will ensure consistency throughout the application.

<p>Mega test: <input type="checkbox" v-model="mega" /></p>
computed: {
  mega: {
    get () {
      return this.$store.getters.mega
    },
    set (value) {
      this.$store.dispatch('updateMega', value)
    }
  }
}
const store = new Vuex.Store({
  state: {
    mega: false
  },
  getters: {
    mega (state) {
      return state.mega
    }
  },
  mutations: {
    updateMega (state, value) {
      state.mega = value
    }
  },
  actions: {
    updateMega (context, value) {
      context.commit('updateMega', value)
    }
  }
})
Reaves answered 5/8, 2020 at 8:55 Comment(0)
F
-2

You shoud remove colours = [] in data.

Fraase answered 29/1, 2018 at 4:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.