Search code examples
vue.jsvuexvuejs3vuex4

Vue.js 3 - How can I pass data between Vue components and let both views also update?


I tried the following. Please note the commented line in parent.vue that doesn't even commit the new state for me. However maybe someone can guide me to a better solution for a global state shared by multiple components?

main.js

import { createApp } from 'vue'
import App from './App.vue'
import { createStore } from 'vuex'

const app = createApp(App);
export const store = createStore({
    state: {
        textProp: 'test',
        count: 1
    },
    mutations: {
        setState(state, newState) {
            console.log('setState');
            state = newState;
        }
    },
    getters: {
        getAll: (state) => () => {
            return state;
        }
    }

});
app.use(store);
app.mount('#app')

parent.vue

<template>
  <div class="parent">
    <div class="seperator" v-bind:key="item" v-for="item in items">
      <child></child>
    </div>
    <button @click="toonAlert()">{{ btnText }}</button>
    <button @click="veranderChild()">Verander child</button>
  </div>
</template>


<script>
import child from "./child.vue";
import {store} from '../main';

export default {
  name: "parent",
  components: {
    child,
  },
  store,
  data: function () {
    return {
      items: [
        {
          id: 1,
          valueText: "",
          valueNumber: 0,
        },
        {
          id: 2,
          valueText: "",
          valueNumber: 0,
        },
        {
          id: 3,
          valueText: "",
          valueNumber: 0,
        },
      ],
    };
  },
  props: {
    btnText: String,
  },
  methods: {
    toonAlert() {
      alert(JSON.stringify(this.$store.getters.getAll()));
    },
     veranderChild() {
       console.log('child aan het veranderen (parentEvent)');
       this.$store.commit('setState', { // This is especially not working.
             textProp: 'gezet via de parent',
             count: 99
         })
       this.$store.commit({type: 'setState'}, {
         'textProp': 'gezet via de parent',
          'count': 99
          });
    },
  },
};
</script>

<style>
.seperator {
  margin-bottom: 20px;
}

.parent {
  /* background: lightblue; */
}
</style>

child.vue

<template>
  <div class="child">
    <div class="inputDiv">
      text
      <input @change="update" v-model="deText" type="text" name="deText" />
    </div>
    <div class="inputDiv">
      nummer
      <input v-model="hetNummer" type="number" name="hetNummer" />
    </div>
    <button @click="toonState">Toon huidige state</button>
  </div>
</template>

<script>

import {store} from '../main';

export default {
  name: "child",
  store,
  data: function() {
    return {
      'hetNummer': 0
    }
  },
  methods: {
    update(e) {
      let newState = this.$store.state;
      newState.textProp = e.target.value;
      // this.$store.commit('setState', newState);
    },
    toonState()
    {
      console.log( this.$store.getters.getAll());
    }
  },
  computed: {
    deText: function() {
      return '';
      // return this.$store.getters.getAll().textProp;
    }
  }
};
</script>

<style>
.inputDiv {
  float: right;
  margin-bottom: 10px;
}
.child {
  max-width: 300px;
  height: 30px;
  margin-bottom: 20px;
  /* background: yellow; */
  margin: 10px;
}
</style>

Solution

  • You have a misconception about JavaScript unrelated to Vue/Vuex. This doesn't do what you expect:

    state = newState;
    

    Solution (TL;DR)

    setState(state, newState) {
      Object.assign(state, newState);
    }
    

    Instead of setting the state variable, merge the new properties in.

    Explanation

    • Object variables in JavaScript are references. That's why if you have multiple variables referring to the same object, and you change a property on one, they all mutate. They're all just referring to the same object in memory, they're not clones.

    The state variable above starts as a reference to Vuex's state object, which you know. Therefore, when you change properties of it, you mutate Vuex's state properties too. That's all good.

    But when you change the whole variable-- not just a property-- to something else, it does not mutate the original referred object (i.e. Vuex's state). It just breaks the reference link and creates a new one to the newState object. So Vuex state doesn't change at all. Here's a simpler demo.

    Opinion

    Avoid this pattern and create an object property on state instead. Then you can just do state.obj = newState.