How to sync states between backend and frontend using vuex and vue-router?
Asked Answered
G

2

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.

Gallopade answered 22/3, 2019 at 14:8 Comment(13)
Hey, maybe you can sync with this: github.com/vuejs/vuex-router-syncPhilbo
I don't understand your backend, is it just another vue file, or an actual server?Resourceful
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.Gallopade
@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.Gallopade
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 toResourceful
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?Gallopade
@Gallopade component properties that are defined in data () => {} are reactive, methods are notCleanup
@OniyaDaniel Yes, but I thought that methods defined in e.g. 'computed ()' are also reactive. Is this a wrong assumption?Gallopade
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.Resourceful
@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'?Gallopade
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.Resourceful
Have you seen the console in f12? because your code sounds ok and maby you should use getter for retrieving the counter.Deanadeanda
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. codesandbox.io/s/…Recession
A
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:

A answered 15/1, 2020 at 14:35 Comment(0)
C
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,
    }
}
Cleanup answered 22/3, 2019 at 15:1 Comment(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.Gallopade

© 2022 - 2024 — McMap. All rights reserved.