VueX actions/mutations error in Chrome console - vue.js

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)
}
}

Related

How to assign new value to computed in VueJS [duplicate]

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'])
}

Clone / Copy state is returning empty state

I am having an issue using lodash's cloneDeep to clone the user object passed in from the store. When I attempt to render the data in the template {{ user }} shows the data retrieved from the store and {{ userCopy }} shows the empty store. I am not sure why this is happening, I am new to Vue.
store/staff.js
import StaffService from '~/services/StaffService.js'
export const state = () => ({
user: {
offers: '',
legal: ''
}
})
export const mutations = {
SET_USER(state, user) {
state.user = user
},
}
export const actions = {
fetchUser({ commit, getters }, id) {
const user = getters.getUserById(id)
if (user) {
commit('SET_USER', user)
} else {
StaffService.getUser(id)
.then((response) => {
commit('SET_USER', response.data)
})
.catch((error) => {
console.log('There was an error:', error.response)
})
}
},
}
export const getters = {
getUserById: (state) => (id) => {
return state.staff.find((user) => user.id === id)
}
}
pages/settings/_id.vue
<template>
<div>
{{ user }} // will display the whole object
{{ userCopy }} // will only display empty store object
</div>
</template>
<script>
import _ from 'lodash'
data() {
return {
userCopy: _.cloneDeep(this.$store.state.staff.user)
}
},
computed: {
...mapState({ user: (state) => state.staff.user })
},
created() {
this.$store.dispatch('staff/fetchUser', this.$route.params.id)
},
</script>
My guess would be that a Vue instance's data is initialized before state becomes available. While computed props are populated/updated as their data source change.
If the component doesn't need to change the value of user during runtime, I'd suggest turning it into a computed property.
If your component does change the value during runtime (such as when it's v-model'd to an input), there are two approaches you can do.
Method 1: Using mounted hook
This is done by placing user in data property and then assigning a value when the instance is mounted, like so:
mounted () {
this.$data.userCopy = _.cloneDeep(this.$store.state.staff.user)
}
Method 2: Using computed with getter and setter functions.
Normally, you shouldn't change a computed value. But it can be done using a setter function. With this, when Vue detects an attempt to change a computed prop it will execute set() with the old and new values as arguments. This function would change the value at its source, allowing get()'s returned value to reflect this. For example:
computed: {
userCopy: {
get () {
return _.cloneDeep(this.$store.state.staff.user)
},
set (newValue) {
this.$store.commit('updateStaff', newValue) // Replace this line with your equivalent state mutator.
}
}
}

Getting notified if the data is loaded when the data is in a computed field

I have a Vue project with vuetify, and i want to use the loading functionality.
The project is set up so the items are loaded with a computed field.
How do i now manipulate the 'loading' property, turning it of when its loaded?
I tried attaching the items async fetching the data with a non-computed field. But the v-data-table is turned into a component and i'm not succeeding.
<data-table
:title="title"
:headers="headers"
:items="allUsers"
:loading="loading"
#editItem="onEdit"
#deleteItem="onDelete" >
</data-table>
The allUsers property is computed
Right now the loaded property is also computed putting a .length on the allusers. This only works if there are users ... if none it keeps spinning.
computed: {
allUsers () {
return this.$store.getters['users/users']
},
loading(){
return this.$store.getters['users/users'] != null;
}
},
Assuming you load users into your store within an action, you should also set some state for loading.
For example (simplified, no module)
const store = {
state: {
users: [],
loading: false
},
mutations: {
setUsers (state, users) {
state.users = users
},
isLoading (state) {
state.loading = true
},
loadingComplete (state) {
state.loading = false
}
},
actions: {
async loadUsers ({ commit }) {
commit('isLoading') // set loading state
commit('setUsers', await fetchUserDataFromSomwhere())
commit('loadingComplete')
}
}
}
Now your components can subscribe to the loading state
computed: {
loading () {
return this.$store.state.loading // or use mapState / mapGetters / whatever
},
allUsers () {
return this.$store.state.users
}
}

async vuex fetch action state filled if using variable in template getting error undefined

i have one async action vuex, im using map getters and component created function to fetch and fill data, if im using this store data inline object in template view console show error undefined, if i try acces variable only without inline object im getting undefined error for inline object, i think this error about async function not blocking main process component fully loaded and after async function filled variable
actions, state
// state
export const state = {
app: null
}
// getters
export const getters = {
app: state => state.app,
}
// mutations
export const mutations = {
[types.FETCH_APP_SUCCESS] (state, { app }) {
state.app = app
},
[types.FETCH_APP_FAILURE] (state) {
state.app = null
},
[types.UPDATE_APP] (state, { app }) {
state.app = app
}
}
async fetchApp ({ commit }) {
try {
const { data } = await axios.get('/api/app/1')
commit(types.FETCH_APP_SUCCESS, { app: data })
} catch (e) {
commit(types.FETCH_APP_FAILURE)
}
}
component
<template>
<div>
{{app.name}}
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
middleware: 'auth',
created () {
// i try here async and await
this.$store.dispatch('app/fetchApp')
},
computed: mapGetters({
app: 'app/app'
}),
metaInfo () {
return { title: this.$t('home') }
}
}
</script>
state is filled
variable can see in html
but console this error
app/app is initially null, and your template does not have a null check on app.name, which results in the error you saw. You can either conditionally render app.name in the template:
<template>
<div>
<template v-if="app">
{{app.name}}
</template>
</div>
</template>
Or use the empty string as app/app's initial state instead of null in your store.

VueJS - Accessing store data inside mounted

I'm having trouble understanding the following:
I have a store which contains variables needed for the application. In particular, there is a globalCompanies which stores:
globalCompanies: {
current: [],
all: [],
currentName: "",
}
Inside another component, I want to do the following:
mounted() {
this.$store.dispatch( "fetchUsers" );
var currentName = this.$store.state.globalCompanies.currentName;
console.log(currentName);
},
However, this just shows as empty. I know the value is there because I have computed which returns the currentName and it works fine inside the view itself. It just doesn't like the fact that it's in the mounted component.
Where am I going wrong and what can I do to resolve this issue? I really need to capture the companies Name in order to use it for some real time events.
As a result of our discussion:
In the question Vuex state value, accessed in component's mounted hook, returns empty value, because it is set in an async action which does not resolve before mounted executes.
When you need to trigger some function when async action in Vuex resolves with a value, you can achieve it using watch on a computed property, which returns a value from your Vuex state. When a value in store changes, the computed property reflects these changes and watch listener executes:
const store = new Vuex.Store({
state: {
globalCompanies: {
test: null
}
},
mutations: {
setMe: (state, payload) => {
state.globalCompanies.test = payload
}
},
actions: {
pretendFetch: ({commit}) => {
setTimeout(() => {
commit('setMe', 'My text is here!')
}, 300)
}
}
})
new Vue({
el: '#app',
store,
computed: {
cp: function() { // computed property will be updated when async call resolves
return this.$store.state.globalCompanies.test;
}
},
watch: { // watch changes here
cp: function(newValue, oldValue) {
// apply your logic here, e.g. invoke your listener function
console.log('was: ', oldValue, ' now: ', newValue)
}
},
mounted() {
this.$store.dispatch('pretendFetch');
// console.log(this.cp, this.$store.state.globalCompanies.test); // null
// var cn = this.$store.state.globalCompanies.test; // null
// console.log(cn) // null
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<script src="https://unpkg.com/vuex#2.3.1"></script>
<div id="app">
{{ cp }}
</div>
VueJS - Accessing Store Data Inside Mounted
Ran into this issue and it turned out to be a scope issue.
Store:
export default () => {
items:[],
globalCompanies:{
current:[],
all:[],
currentName: "Something"
},
ok: "Here you go"
}
Getters:
export default {
getGlobalCompanies(state){
return state.globalCompanies;
}
}
Mounted: This works...
mounted() {
// Initialize inside mounted to ensure store is within scope
const { getters } = this.$store;
const thisWorks = () => {
const globalCompanies = getters.getGlobalCompanies;
}
},
This is Bad: Reaching for the store outside the mounted scope
mounted() {
function ThisDontWork() {
const { getters } = this.$store; // this.$store == undefined
}
ThisDontWork();
},