Catatan Belajar Vue : Vuex
Target tulisan kali ini adalah bisa membuat satu buah fitur crud menggunakan vuex. Lalu apa itu vuex ? Kalau dibaca dari situs ofisialnya sih vuex itu state management pattern + library, tapi setelah itu ada pertanyaan lagi state management itu apa ? Kalau yang saya pahami sih state management atau vuex ini berfungsi sebagai “sumber kebenaran” ( source of truth ) sehingga data yang sama dapat dimanfaatkan lintas komponen. Mohon koreksinya jika pemahaman saya salah.
Persiapan
Di sini saya akan buat aplikasi crud dari vue, maka dari itu akan dibutuhkan beberapa halaman, diantaranya :
List.vue sebagai halaman daftar data yang tersedia
<template>
<v-layout row wrap>
<v-btn light to="peserta/tambah">Tambah Peserta </v-btn>
<v-data-table v-bind:headers="headers" :items="items" hide-actions class="">
<template slot="items" scope="props">
<td class="text-xs-right">{{ props.item.tim }}</td>
<td class="text-xs-right">{{ props.item.qualified | masklabel}}</td>
<td class="text-xs-right">
<v-btn color="primary" :to="{ name: 'edit', params: { id: props.item.id }}">Ubah</v-btn>
<v-dialog v-model="dialog" max-width="600px">
<v-btn color="error" dark slot="activator" >Hapus</v-btn>
<v-card>
<v-card-title>
<span class="headline">Anda yakin untuk menghapus data ini?</span>
</v-card-title>
<v-card-text><h3>Hapus</h3></v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="green darken-1" flat="flat" @click="dialog = false">Tidak</v-btn>
<v-btn color="green darken-1" flat="flat" >Ya</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</td>
</template>
</v-data-table>
<v-flex xs12 >
<!-- <v-pagination :length="length" v-model="page" ></v-pagination> -->
</v-flex>
</v-layout>
</template>
<script>
export default {
data () {
return {
dialog: false,
selected: [],
headers: [
{ text: 'Tim', value: 'tim' },
{ text: 'Status', value: 'status' },
{ text: 'Action', value: 'action' }
],
items: [
{
id : 1,
tim: 'Ac Milan',
qualified: true
},
{
id: 2,
tim: 'Parma',
qualified: false
}
]
}
},
filters: {
masklabel(value) {
return (value) ? 'Lolos' : 'Tidak lolos'
}
}
}
</script>
Add.vue sebagai komponen menambah data
<template>
<v-form v-model="valid" ref="form" lazy-validation>
<v-text-field
label="Team"
v-model="team"
:rules="teamRules"
required
></v-text-field>
<v-select
label="Status"
v-model="select"
:items="items"
:rules="[v => !!v || 'Item is required']"
required
></v-select>
<v-btn
@click="submit"
:disabled="!valid"
>
submit
</v-btn>
<v-btn @click="clear">clear</v-btn>
</v-form>
</template>
<script>
export default {
data: () => ({
valid: true,
team: '',
teamRules: [
(v) => !!v || 'Team is required',
],
select: null,
items: [
'Lolos',
'Tidak Lolos'
]
}),
methods: {
submit () {
if (this.$refs.form.validate()) {
}
},
clear () {
this.$refs.form.reset()
}
}
}
</script>
Edit.vue sebagai komponen ubah data.
<template>
<v-form v-model="valid" ref="form" lazy-validation>
<v-text-field
label="Team"
v-model="team"
:rules="teamRules"
required
></v-text-field>
<v-select
label="Status"
v-model="select"
:items="items"
:rules="[v => !!v || 'Item is required']"
required
></v-select>
<v-btn
@click="submit"
:disabled="!valid"
>
submit
</v-btn>
<v-btn @click="back">Back</v-btn>
</v-form>
</template>
<script>
export default {
data: () => ({
valid: true,
team: '',
teamRules: [
(v) => !!v || 'Team is required',
],
select: '',
items: [
'Lolos',
'Tidak Lolos'
],
checkbox: false
}),
created() {
},
methods: {
fetch(value) {
},
submit () {
if (this.$refs.form.validate()) {
}
},
back () {
}
}
}
</script>
Komponen yang dibutuhkan sudah disiapkan, sekarang saatnya mengatur router, buka index.js dalam folder router, lalu ubah menjadi seperti berikut :
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
import List from '@/components/List'
import Add from '@/components/Add'
import Edit from '@/components/Edit'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Root',
component: Home
},
{
path: '/list',
name: 'List',
component: List
},
{
path: '/peserta/tambah',
name: 'add',
component: Add
},
{
path: '/peserta/:id/edit',
name: 'edit',
component: Edit
}
],
mode: 'history'
})
Seharusnya saat aplikasi diakses akan menghasilkan seperti berikut

Vuex
Gunakan perintah berikut untuk mengunduh paket vuex
npm install --save vuex
Sejajar dengan berkas main.js buat satu berkas baru namanya store.js ( lokasi nama bebas sih sesuaikan dengan keinginan), dalam struktur dasar store.js sendiri sebagai berikut
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
},
getters: {
},
mutations: {
}
})
State : Ini adalah “sumber kebenaran” itu, konten state di sini bisa disalin dari data di komponen, maka hasilnya akan seperti konten di bawah ini.
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
participants: [{
id: 1,
tim: 'Ac Milan',
qualified: true
},
{
id: 2,
tim: 'Parma',
qualified: false
}
]
},
getters: {
},
mutations: {
}
})
Di state saya tambahkan properti ‘participants’ yang isinya itu hasil salin dari properti `items‘ di komponen List.vue. Dan vuex menganut single state tree yang artinya vuex hanya boleh menyediakan punya satu sumber saja.Kalau mau punya banyak sumber bisa menggunakan vuex module, tapi itu dibahas nanti.
Getters : Sesuai namanya Get (mengambil) Getters berfungsi untuk mengambil data dari state, dengan getters kita bisa mengatur data yang akan dimunculkan, mari tambahkan dulu getters pada store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
participants: [{
id: 1,
tim: 'Ac Milan',
qualified: true
},
{
id: 2,
tim: 'Parma',
qualified: false
}
]
},
getters: {
all(state) {
return state.participants
},
isQualified(state) {
return state.participants.filter(participant => {
return participant.qualified
})
},
notQualified(state) {
return state.participants.filter(participant => {
return !participant.qualified
})
},
detail: (state, getters) => (id) => {
return state.participants.find(participant => participant.id == id)
}
},
mutations: {
}
})
Di sana ada 4 buah method di dalam getters,
all : Mengambil semua data participant ( sebenarnya ini gak perlu sih, hanya sebagai contoh maka saya tambahkan )
isQualified : Mengambil semua data participants yang qualified nya true
notQualified : Mengambil semua data participants yang qualified nya false
Detail : Yang ini agak berbeda karena ini akan mampu menerima parameter yang dikirim dari komponen.
Terakhir, Mutations : Seperti disinggung di atas saya akan mencoba membuat aplikasi crud yang berarti di sini saya akan memanipulasi ( menambah, mengubah, dan menghapus ) data dari state. Menurut peraturan yang ada kita tidak boleh mengubah langsung kepada state, kita harus melalui mutations terlebih dahulu. Berikut mutations yang saya buat untuk mengakomodir kebutuhan crud
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
participants: [{
id: 1,
tim: 'Ac Milan',
qualified: true
},
{
id: 2,
tim: 'Parma',
qualified: false
}
]
},
getters: {
all(state) {
return state.participants
},
isQualified(state) {
return state.participants.filter(participant => {
return participant.qualified
})
},
notQualified(state) {
return state.participants.filter(participant => {
return !participant.qualified
})
},
detail: (state, getters) => (id) => {
return state.participants.find(participant => participant.id == id)
}
},
mutations: {
register(state, data) {
state.participants.push(data)
},
update(state, data) {
var participant = state.participants.find(o => o.id == data.id)
var index = state.participants.indexOf(participant)
state.participants[index].tim = data.tim
state.participants[index].qualified = data.qualified
},
delete(state, value) {
var participant = state.participants.find(o => o.id == value)
state.participants.splice(state.participants.indexOf(participant), 1)
}
}
})
Di sana ada 3 mutation strukturnya mirip dengan getters namun mutation bisa dikirimi parameter dengan mudah ( tidak seperti getter yang struktur nya berubah ketika ingin membaca parameter), Baik getters dan mutations wajib menyertakan parameter state agar bisa mengakses state yang tersedia.
Adapun fungsi di atas :
register : Bertanggung jawab menambahkan data ke state
update : Bertanggung jawab mengubah data ke state
delete : Bertanggung jawab menghapus data dari state
Setelah store disiapkan maka panggil store.js di main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import Vuetify from 'vuetify'
import './stylus/main.styl'
import App from './App'
import router from './router'
import {store} from './store'
Vue.use(Vuetify)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
template: '<App/>',
router,
store,
components: { App }
})
Persiapan vuex selesai.
CRUD
Setelah siap semuanya sekarang tinggal memanfaatkan saja, ubah List.vue menjadi seperti berikut
List.vue
<template>
<v-layout row wrap>
<v-btn light to="peserta/tambah">Tambah Peserta </v-btn>
<v-data-table v-bind:headers="headers" :items="items" hide-actions class="">
<template slot="items" scope="props">
<td class="text-xs-right">{{ props.item.tim }}</td>
<td class="text-xs-right">{{ props.item.qualified | masklabel}}</td>
<td class="text-xs-right">
<v-btn color="primary" :to="{ name: 'edit', params: { id: props.item.id }}">Ubah</v-btn>
<v-dialog v-model="dialog" max-width="600px">
<v-btn color="error" dark slot="activator" @click="modalConfirm(props.item.id)" >Hapus</v-btn>
<v-card>
<v-card-title>
<span class="headline">Anda yakin untuk menghapus data ini?</span>
</v-card-title>
<v-card-text><h3>{{ selectname }}</h3></v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="green darken-1" flat="flat" @click="dialog = false">Tidak</v-btn>
<v-btn color="green darken-1" flat="flat" @click="confirmDelete(selectedId)">Ya</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</td>
</template>
</v-data-table>
<v-flex xs12 >
<!-- <v-pagination :length="length" v-model="page" ></v-pagination> -->
</v-flex>
</v-layout>
</template>
<script>
export default {
data () {
return {
search : '',
dialog: false,
selectname: '',
selectedId: '',
selected: [],
headers: [
{ text: 'Tim', value: 'tim' },
{ text: 'Status', value: 'status' },
{ text: 'Action', value: 'action' }
],
items: [
]
}
},
created() {
this.fetch()
},
methods: {
fetch() {
this.items = this.$store.getters.all
//bisa juga langsung ke state
// this.$store.state.participants
// kalau untuk getter strkturnya : this.$store.getters.namagetter
},
modalConfirm(value) {
var participant = this.items.find(o => o.id === value);
this.selectname = participant.tim
this.selectedId = participant.id
},
confirmDelete(value) {
this.$store.commit('delete',value)
this.dialog = false
}
},
filters: {
masklabel(value) {
return (value) ? 'Lolos' : 'Tidak lolos'
}
}
}
</script>
Add.vue
<template>
<v-form v-model="valid" ref="form" lazy-validation>
<v-text-field
label="Team"
v-model="team"
:rules="teamRules"
required
></v-text-field>
<v-select
label="Status"
v-model="select"
:items="items"
:rules="[v => !!v || 'Item is required']"
required
></v-select>
<v-btn
@click="submit"
:disabled="!valid"
>
submit
</v-btn>
<v-btn @click="clear">clear</v-btn>
</v-form>
</template>
<script>
export default {
data: () => ({
valid: true,
team: '',
teamRules: [
(v) => !!v || 'Team is required',
],
select: null,
items: [
'Lolos',
'Tidak Lolos'
]
}),
methods: {
submit () {
if (this.$refs.form.validate()) {
// Native form submission is not yet supported
var data = {
id: Math.round((new Date()).getTime() / 1000),
tim: this.team,
qualified: (this.select == 'Lolos') ? true : false
}
this.$store.commit('register',data)
this.$router.push('/list')
}
},
clear () {
this.$refs.form.reset()
}
}
}
</script>
Edit.vue
<template>
<v-form v-model="valid" ref="form" lazy-validation>
<v-text-field
label="Team"
v-model="team"
:rules="teamRules"
required
></v-text-field>
<v-select
label="Status"
v-model="select"
:items="items"
:rules="[v => !!v || 'Item is required']"
required
></v-select>
<v-btn
@click="submit"
:disabled="!valid"
>
submit
</v-btn>
<v-btn @click="back">Back</v-btn>
</v-form>
</template>
<script>
export default {
data: () => ({
valid: true,
team: '',
teamRules: [
(v) => !!v || 'Team is required',
],
select: 'Lolos',
items: [
'Lolos',
'Tidak Lolos'
],
checkbox: false
}),
created() {
this.fetch(this.$route.params.id);
},
methods: {
fetch(value) {
console.log(value)
var participant = this.$store.getters.detail(value)
this.team = participant.tim
this.select = (participant.qualified) ? "Lolos" : "Tidak Lolos"
},
submit () {
if (this.$refs.form.validate()) {
// Native form submission is not yet supported
var data = {
id: this.$route.params.id,
tim: this.team,
qualified: (this.select == 'Lolos') ? true : false
}
this.$store.commit('update',data)
this.$router.push('/list')
}
},
back () {
this.$router.push('/list')
}
}
}
</script>
Antara mengakses getter dan mutation terlihat perbedaanya, memanggil getter bisa langsung diakses seperti berikut this.$store.getters.namagetter sedangkan untuk mutations dengan cara this.$store.commit('namamutation',parameter).
Semuanya sudah maka jika apliaksi dijalankan akan seperti berikut

Jika ingin lihat seluruh source nya :
https://gitlab.com/ariesmaulana/belajarvue
Lalu pilih branch vuex.
Referensi :
https://vuex.vuejs.org/en/intro.html
https://www.youtube.com/watch?v=2CSr2vBApSI&list=PL55RiY5tL51pT0DNJraU93FhMzhXxtDAo