12

I am developing a single-page-application using vue-cli3 and npm.

The problem: Populating a basic integer value (stored in a vuex state) named counter which was incremented/decremented in the backend to the frontend, which displays the new value.

The increment/decrement mutations are working fine on both components (Frontend/Backend), but it seems like the mutations don't work on the same route instance: When incrementing/ decrementing the counter in backend, the value is not updated in the frontend and otherwise.

store.js:

Contains the state which needs to be synced between Backend/Frontend.

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    counter: 10
  },
  mutations: {
    increment (state) {
      state.counter++
    },
    decrement (state) {
      state.counter--
    }
  }
})

index.js:

Defines the routes that the vue-router has to provide.

import Vue from 'vue'
import Router from 'vue-router'
import Frontend from '@/components/Frontend'
import Backend from '@/components/Backend'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Frontend',
      component: Frontend
    },
    {
      path: '/backend',
      name: 'Backend',
      component: Backend
    }
  ],
  mode: 'history'
})

main.js:

Inits the Vue instance and provides the global store and router instances.

import Vue from 'vue'
import App from './App'
import router from './router'
import { sync } from 'vuex-router-sync'
import store from './store/store'

Vue.config.productionTip = false

sync(store, router)

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

Frontend.vue/Backend.vue:

Both (Frontend/Backend) use the same code here. They use the state counter in order to display and modify it.

<template>
  <div> Counter: {{ getCounter }}
    <br>
    <p>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
    </p>
  </div>
</template>

<script>
export default {
  name: 'Frontend',
  methods: {
    increment () {
      this.$store.commit('increment')
    },
    decrement () {
      this.$store.commit('decrement')
    }
  },
  computed: {
    getCounter () {
      return this.$store.state.counter
    }
  }
}
</script>

It would be awesome if someone sould tell me what I am missing or if I have misunderstood the concept of vuex and vue-router.

sebikolon
  • 121
  • 1
  • 7
  • 1
    Hey, maybe you can sync with this: https://github.com/vuejs/vuex-router-sync – Leonard Klausmann Mar 22 '19 at 14:19
  • I don't understand your backend, is it just another vue file, or an actual server? – Len Joseph Mar 22 '19 at 14:26
  • Thanks @LeonardKlausmann, but I am already using vuex-router-sync, as you can see in **sync(store, router)** in **main.js**. But i guess this is just for syncing the current route of vue-router in a vuex-state. Hence not the solution for my problem. – sebikolon Mar 22 '19 at 14:27
  • @I'mOnlyVueman it is just another vue file with the same content as in Frontend.vue. I tried to keep the application as simple as possible in order to get vuex working. – sebikolon Mar 22 '19 at 14:29
  • Then what you need to do if you want the state to reflect the backend file is: 1) create a prop in your backend that holds the current counter value 2) in the frontend, create a beforeMount() function that takes the backend prop as a parameter and uses it as a payload to a vuex mutation. Recommend using mapMutations to trigger the mutation from the function. 3) when the frontend is created, the beforeMount function would read the prop from backend, then commit a mutation with the prop value to update the state. 4) By the time frontend mounts, state should have the value you want it to – Len Joseph Mar 22 '19 at 14:32
  • Thanks @I'mOnlyVueman for your detailed reply. Can you explain me how to 'take the backend prop as a parameter' ? This is what I have atm: **beforeMount: { getCounterFromBackend (backendCounter) { this.$store.commit('set', backendCounter) } }**. But I don't know where to get 'backendCounter' from? – sebikolon Mar 22 '19 at 14:56
  • @sebikolon component properties that are defined in `data () => {}` are reactive, methods are not – Oniya Daniel Mar 22 '19 at 14:57
  • @OniyaDaniel Yes, but I thought that methods defined in e.g. 'computed ()' are also reactive. Is this a wrong assumption? – sebikolon Mar 22 '19 at 15:00
  • To my knowledge this would require an event bus, per the docs. A better question is, why are you using this design pattern? What you are better off doing is having your state be the "single source of truth", and using that to feed both your frontend and backend components. If you keep passing data between components, you will have a bad time. Better off using the central store for component-shared data. – Len Joseph Mar 22 '19 at 15:22
  • @I'mOnlyVueman That's what I intended to achieve! I guess that vuex fits for my requirements because I want a central store where the current value of **counter** is located at. Unluckily I cannot get it working with Frontend and Backend despite it should. Are there other possibilites to achieve a 'single source of truth'? – sebikolon Mar 22 '19 at 15:26
  • I must have misunderstood your intent - my mistake. If the backend file controls the counter, and the frontend file renders the counter, here is how to proceed: 1) trigger the mutation from the backend file. Again, mapMutations would be useful, or you can just commit the action. 2) Create a getter in your store that returns the counter from state. 3) In your frontend use the getter as a computed property. mapGetters helper would be good here. 4) render the computed property in the template. – Len Joseph Mar 22 '19 at 19:52
  • Have you seen the console in f12? because your code sounds ok and maby you should use getter for retrieving the counter. – Ali Hallaji Mar 22 '19 at 23:47
  • The Router and State have nothing to do with each other. The routing renders the specified Vue component for that path. If both components are accessing the same property in the store's state it should return that same value. From the code your provided it should work without problem, however I noticed that you specified the component name as "Frontend" for both files. This shouldn't really matter but just in-case. I've got it working here. Same as your code just using mapState and mapMutations instead. https://codesandbox.io/s/green-shadow-bq162?fontsize=14&hidenavigation=1&theme=dark – James Totty Jan 12 '20 at 01:16

2 Answers2

2

Just get the counter from the store for both components. You don't need data as store is already reactive.

<template>
  <div> Counter: {{ counter }}
    <br>
    <p>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
    </p>
  </div>
</template>

<script>
import { mapState, mapMutations } from 'vuex';

export default {
  name: 'Frontend',
  methods: {
    ...mapMutations([
      'increment',
      'decrement',
    ])
  },
  computed: {
    ...mapState({
        counter: state => state.counter,
    })
  }
}
</script>

For reference:

rmiguelrivero
  • 926
  • 8
  • 8
0

@sebikolon component properties that are defined in data () => {} are reactive, methods are not, they are called once. Instead of {{ getCounter }}, just use {{ $store.state.counter }}. OR initiate property in each component that gets the value of your state.

data: function () {
    return {
        counter: $store.state.counter,
    }
}
Oniya Daniel
  • 359
  • 6
  • 15
  • 1
    When I reference the state like `{{ $store.state.counter }}` in Frontend, I end up with having a worse situation than before. The value of **counter** is not updated anymore (with using method **getCounter()** it worked before, but limited to the Frontend component). And also in the Backend the state is not updated, as this was the reason for my question. – sebikolon Mar 22 '19 at 15:19