I'm trying to create a "settings" component which saves selected values into a store so that all the other components can use those values to change their appearance.
SettingsView.vue:
One of the settings (you can also see it on codepen):
[...]
<p>{{ themeColor }}</p>
<v-radio-group v-model="themeColor">
<v-radio label="light" value="light"></v-radio>
<v-radio label="dark" value="dark"></v-radio>
</v-radio-group>
[...]
<script>
export default {
data () {
return {
// default value
themeColor: 'light',
}
},
computed: {
themeColor () {
return this.$store.state.themeColor
}
},
methods: {
changeThemeColor() {
this.$store.commit('changeThemeColor')
},
}
}
</script>
I don't know how to properly send the selected value of that setting to the store so I just created a mutation with a method (plus the need to have some default value, e.g. themeColor: 'light' like shown above, make it more confusing)
store/modules/Settings.js
const state = {
themeColor: ''
}
const mutations = {
changeThemeColor: state => {
state.themeColor = ''
}
}
export default {
state,
mutations
}
How do I do this properly, so I can then use that value in all the components?
Do I have to use something like getters or actions? I don't really know.
From https://vuex.vuejs.org/en/forms.html, I would use a computed property with getter and setter, ie
export default {
computed: {
themeColor: {
get () {
return this.$store.state.themeColor
},
set (value) {
this.$store.commit('changeThemeColor', value)
}
}
}
}
Note, you do not need data or methods.
Your store should also look more like
const state = {
themeColor: 'light' // default value
}
const mutations = {
changeThemeColor (state, themeColor) {
state.themeColor = themeColor
}
}
Demo ~ https://codepen.io/anon/pen/YYbPww?editors=1011
For instances where you just want to display / read the themeColor state in your component, I recommend using the mapState helper.
import { mapState } from 'vuex'
export default {
// ...
computed: mapState(['themeColor'])
}
Related
GOAL: I would like to watch a vuex state object (MATCH_DATA) for changes, specific to the value of a prop (topicId). So, I would like to set the watcher to watch MATCH_DATA[topicId]. And whenever MATCH_DATA[topicId] updates, I'd like to call a function (in this early case just logging it).
However, since MATCH_DATA is a getter how would I pass a parameter to it? The vuex getter does not seem to be designed to take in parameters. I have seen some examples by explicitly calling this.$store.state.etc.etc. but is there a way to do this while retaining the three dot notation I currently have?
Or, is there some better way of achieving this goal that does not involve a vuex getter?
Current Code:
Nuxt component:
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
props: ['topicId'],
computed: {
...mapGetters('sessionStorage', ['MATCH_DATA']),
},
watch: {
MATCH_DATA (newMatchData, oldMatchData) {
console.log(newMatchData);
}
},
mounted() {
this.SUBSCRIBE_TO_TOPIC(this.topicId);
},
methods: {
...mapActions('sessionStorage', ['SUBSCRIBE_TO_TOPIC'])
}
}
vuex store:
/* State */
export const state = () => ({
MATCHES: {},
});
/* GETTERS */
export const getters = {
MATCH_DATA: (state) => {
return state.MATCHES;
},
};
Your getter can simply return a function like this:
export const getters = {
MATCH_DATA: (state) => {
return topicId => {
// return something
}}
},
};
Then you can create a computed property to access those getter:
export default {
computed: {
...mapGetters('sessionStorage', ['MATCH_DATA']),
yourComputedProperty () {
return this.MATCH_DATA(this.topicId)
}
}
}
Then you can watch the computed property to react on changes.
watch: {
yourComputedProperty (newData, oldData) {
console.log(newData);
}
}
I am building an application which is using Vue 3 and I am providing a property in a parent component which I am subsequently injecting into multiple child components. Is there any way for a component which gets injected with this property to watch it for changes?
The parent component looks something like:
<template>
<child-component/>
<other-child-component #client-update="update_client" />
</template>
<script>
export default {
name: 'App',
data() {
return {
client: {}
}
},
methods: {
update_client(client) {
this.client = client
}
},
provide() {
return {
client: this.client
}
},
}
</script>
The child component looks like:
<script>
export default {
name: 'ChildComponent',
inject: ['client'],
watch: {
client(new_client, old_client) {
console.log('new client: ', new_client);
}
}
}
</script>
I am trying to accomplish that when the provided variable gets updated in the parent the children components where its being injected should get notified. For some reason the client watch method is not getting called when client gets updated.
Is there a better way of accomplishing this?
Update
After further testing I see that there is a bigger issue here, in the child component even after the client has been updated in the parent, the client property remains the original empty object and does not get updated. Since the provided property is reactive all places it is injected should automatically be updated.
Update
When using the Object API reactive definition (data(){return{client:{}}), even though the variable is reactive within the component, the injected value will be static. This is because provide will set it to the value that it is initially set to. To have the reactivity work, you will need to wrap it in a computed
provide(){
return {client: computed(()=>this.client)}
}
docs:
https://vuejs.org/guide/components/provide-inject.html#working-with-reactivity
You may also need to use deep for your watch
Example:
<script>
export default {
name: 'ChildComponent',
inject: ['client'],
watch: {
client: {
handler: (new_client, old_client) => {
console.log('new client: ', new_client);
},
deep: true
}
}
}
</script>
As described in official documentation ( https://v2.vuejs.org/v2/api/#provide-inject ), by default, provide and inject bindings are not reactive. But if you pass down an observed object, properties on that object remain reactive.
For objects, Vue cannot detect property addition or deletion. So the problem in your code might be here:
data() {
return {
client: {}
}
},
Since you change the client property of this object ( this.client.client = client ), you should declare this key in data, like this:
data() {
return {
client: { client: null }
}
},
Now it becomes reactive.
I did a code sandbox reproducing your code watching an injected property: https://codesandbox.io/s/vue-inject-watch-ffh2b
For some reason the only way I got this to work was by only updating properties of the initial injected object instead of replacing the whole object. I also was not able to get watch working with the injected property despite setting deep: true.
Updated parent component:
<template>
<child-component/>
<other-child-component #client-update="update_client" />
</template>
<script>
export default {
name: 'App',
data() {
return {
client: {}
}
},
methods: {
update_client(client) {
this.client.client = client
}
},
provide() {
return {
client: this.client
}
},
}
</script>
Updated child component:
<template>
<button #click="get_client">Get client</button>
</template>
<script>
export default {
name: 'ChildComponent',
inject: ['client'],
methods: {
get_client() {
console.log('updated client: ', client);
}
}
}
</script>
create a new value and reference the value from inject into it
inject: ['client'],
data: () => ({
value: null,
}),
created() {
this.value = this.client;
},
watch: {
value: {
handler() {
/* ... */
},
deep: true,
}
}
Now you can watch the value.
Note: "inject" must be an object
I ran into the same issue. But i just had to look more closely for details in the docs to make it work. In the end everything worked fine for me.
I built a vue plugin providing a Map together with some function as a readonly ref. Then it starts changing the Map contents once a second:
plugin.js
import { ref, readonly } from 'vue';
const rRuns = ref( new Map() );
let time = 0;
export default
{
install(app, defFile)
{
...
app.provide( "runs", readonly(
{ ref: rRuns,
get: (e) => rRuns.value.get( e ),
locationNames: () => rRuns.value.keys(),
size: () => rRuns.value.size,
} ) );
...
setInterval( () =>
{ time++;
const key = (time * 7) % 10;
console.log(" runs update", key, time);
rRuns.value.set( key.toString(), time )
}, 1000);
console.log(" time Interval start" );
}
}
main.js:
import { createApp } from 'vue'
import App from './App.vue'
import plugin from 'plugin.js';
const app = createApp(App);
app.config.unwrapInjectedRef = true;
app.use(game, 'gamedefs.json');
app.mount('#app');
runs.vue:
<template>
<h1>Runs:</h1>
<p v-if="!runs.size()">< no runs ></p>
<p v-else>runs: {{ runs.size() }}</p>
<button v-for="r of runs.locationNames()" :key="r" #click="display( r )">[{{ r }}]</button>
</template>
<script>
export default {
name: 'Runs',
inject:
{
runs: { from: 'runs' },
},
watch:
{
'runs.ref':
{
handler( v )
{
console.log("runs.ref watch", v );
},
immediate: true,
deep: true,
},
},
}
</script>
I try to store drawer data in VueX to use it on external component.
My console error: [vuex] unknown action type: app/switchDrawer
My VueJS template:
pages/test.vue
<template>
<v-navigation-drawer v-model="drawer" app>
<v-list dense>
...
</v-list>
</v-navigation-drawer>
</template>
<script>
export default {
computed: {
drawer: {
get () {
return this.$store.state.app.drawer
},
set (value) {
console.log(value);
return this.$store.dispatch('app/toggleDrawer', value)
}
}
}
}
</script>
The console.log() function give me lot of lines in loop in console.
I'd like to use too the mapGetters class from VueX instead computed get/set:
computed: mapGetters({
drawer: 'app/drawer'
})
I've an error in console:
[Vue warn]: Computed property "drawer" was assigned to but it has no
setter.
My VueX store:
store/app.js
export const state = () => ({
drawer: true
})
export const getters = {
drawer: state => state.drawer
}
export const mutations = {
TOGGLE_DRAWER: (state) => {
state.drawer = !state.drawer
}
}
export const actions = {
toggleDrawer ({ commit }, value) {
commit('TOGGLE_DRAWER', value)
}
}
IN CASE YOU DON'T WANT TO MAKE A NEW MUTATION AND HANDLE LOCALLY. (which I preferred personally as my store is pretty big already)
Faced similar issue using when using a vue-ui library(vuesax)
Solved it by initializing a new data variable to a computed variable (the one from the store) in created hook
(Why in created hook)
created() {
this.localDrawer = this.drawer
},
data() {
return {
localDrawer: ''
}
},
computed: {
...mapGetters({
drawer: 'drawer'
})
},
watch: {
drawer(newValue, oldValue) {
this.localDrawer = newValue
}
}
Now use localDrawer in the you app.
NOTE: I am watching the drawer variable as well. So that in any case if its value changes it gets reflected.
Found your problem - a computed setter has to have no return statement.
drawer: {
get () {
return this.$store.state.app.drawer
},
set (value) {
this.$store.dispatch('app/toggleDrawer', value)
}
}
Please notice that your action submits a value to the mutation which dosen't take any value. So better add a new mutation that handles said value:
export const mutations = {
SET_DRAWER: (state, value) => {
state.drawer = value
}
}
export const actions = {
toggleDrawer ({ commit }, value) {
commit('SET_DRAWER', value)
}
}
I have a vuex in module mode that fetching the data of a user:
store/modules/users.js
import axios from "axios";
export const state = () => ({
user: {}
});
// Sets the values of data in states
export const mutations = {
SET_USER(state, user) {
state.user = user;
}
};
export const actions = {
fetchUser({ commit }, id) {
console.log(`Fetching User with ID: ${id}`);
return axios.get(`${process.env.BASE_URL}/users/${id}`)
.then(response => {
commit("SET_USER", response.data.data.result);
})
.catch(err => {
console.log(err);
});
}
};
// retrieves the data from the state
export const getters = {
getUser(state) {
return state.user;
}
};
then on my template pages/users/_id/index.vue
<b-form-input v-model="name" type="text"></b-form-input>
export default {
data() {
return {
name: ""
}
},
created() {
// fetch user from API
this.$store.dispatch("fetchUser", this.$route.params.id);
}
}
Now I check the getters I have object getUser and I can see the attribute. How can I assign the name value from vuex getters to the input field?
watcher is probably what you need
export default {
// ...
watch: {
'$store.getters.getUser'(user) {
this.name = user.name;
},
},
}
While Jacob's answer isn't necessarily incorrect, it's better practice to use a computed property instead. You can read about that here
computed: {
user(){
return this.$store.getters.getUser
}
}
Then access name via {{user.name}} or create a name computed property
computed: {
name(){
return this.$store.getters.getUser.name
}
}
Edit: fiddle as example https://jsfiddle.net/uy47cdnw/
Edit2: Please not that if you want to mutate object via that input field, you should use the link Jacob provided.
I have a global.js and I'm emitting an event to global.js What I want to achieve is whenever the value of my global.js re-render the vue.
global.js
export let globalStore = new Vue({
data: {
translateBool: 0,
about: [`About Us`,`フィリピンのマニラに 2015年9月に設立。`]
},
methods: {
changeLanguage(){
if(this.translateBool == 0){
this.translateBool= 1
}else{
this.translateBool= 0
}
}
}
})
globalStore.$on('changeLanguage',globalStore.changeLanguage)
click.vue
import { globalStore } from '../../global.js';
export default{
name: "sample",
data(){
return{
language: globalStore.translate
}
},
methods : {
changeLanguage(){
globalStore.$emit('changeLanguage')
},
}
}
}
</script>
{{language}}
Even though translateBool is = 1, the output doesn't change
The data properties set in the data method are only set once during the Vue instance's initialization.
If you want the language property to update based on the current state of the globalStore.language value, you should make it a computed property:
export default {
name: "sample",
computed: {
language() {
return globalStore.translate
}
},
methods: {
changeLanguage() {
globalStore.$emit('changeLanguage')
}
}
}