Tom Harpaz
Tom Harpaz

Reputation: 489

cannot read property 'dispatch' of undefined in Vuex

I'm trying to perform a dispatch on 'logOutUser' in vuex store, and i'm getting the following error message in respone:

TypeError: Cannot read property 'dispatch' of undefined

deleteUser.vue (the component from which the dispatch action is not working):

<template>
    <v-dialog v-model="openDelete" persistent max-width="500px">
        <v-card>
            <v-card-title>
                <h4>Delete User</h4>
            </v-card-title> 
            <v-card-text>
                <h2>Are You Sure?</h2>
                <p>Deleting your user is a permanent action.</p>
                <br>
                <br>
                <v-btn
                 color="primary"
                 @click="deleteUser">
                 Delete User
                </v-btn>
                <v-btn
                 color="primary"
                 @click="openDelete = false">
                 Close
                </v-btn>  
            </v-card-text>  
        </v-card>   
    </v-dialog> 
</template>
<script>
import router from '../../router/index.js'
const firebase = require('../../firebaseConfig.js')
export default {
    data: function(){
        return {
            openDelete: true
        }
    },
    methods: {
        deleteUser: function(){
            let user = firebase.auth.currentUser
            const docId = user.uid
            console.log("Trying to delete user ", docId)
            user.delete().then(function() {
            }).catch(function(error) {
                console.error("Error deleting user: ", error)
            });
            firebase.firestore.collection("users").doc(docId).delete().then(() => {
                        console.log('Trying to Log Out in Vuex')
                        this.$store.dispatch('user/logOutUser')
                        alert("User Deleted Successfully!")
                }).catch(function(error) {
                    console.error("Error removing document: ", error);
                });
            router.push('hello')    
            this.openDelete = false
        }
    }
}
</script>

store.js:

import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import genre from './modules/genre'

Vue.use(Vuex)

export default new Vuex.Store({
    modules: {
        user,
        genre
    }
})

user.js:

const firebase=require('../firebaseConfig.js')

const state = {
    currentUser: null,
    userProfile: {}
}

const actions = {
        fetchUserProfile({commit, state}, uid){
            firebase.firestore.collection("users").doc(uid).get().then((doc)=>{
                commit('setUserProfile', doc.data())
            }).catch((error)=>{
                console.error(error)
            })
        },
        logOutUser({commit, state}){
            commit('setCurrentUser', null)
            commit('setUserProfile', {})
            console.log('Logged Out In Vuex')
        }
}

const mutations = 
    {
        setCurrentUser(state, val){
            state.currentUser = val
        },
        setUserProfile(state, val){
            state.userProfile = val
        }
    }

export default {
    namespaced: true,
    state,
    actions,
    mutations
}   

EDIT: Here's my main.js file:

import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store.js'
const firebase = require('./firebaseConfig.js')

Vue.config.productionTip = false

let app

firebase.auth.onAuthStateChanged(user => {
  if(!app){
    /* eslint-disable no-new */
    app = new Vue({
      el: '#app',
      router,
      store,
      components: { App },
      template: '<App/>'
    })
  }
});

I should mention that i'm dispatching this action from another component in my application, and it works perfectly fine from there.

Thank you!

Upvotes: 36

Views: 60817

Answers (10)

AMIT DATTA
AMIT DATTA

Reputation: 51

I have faced the same issue because missed importing "store". Solved it as :

import store from './store'

new Vue({
  el: '#app',
  store,
  render: h => h(App),
})

Upvotes: 0

Ryan
Ryan

Reputation: 3171

I had this issue as well, and it turned out I had imported Vue and Vuex with capitals, instead of with lowercased names. This is the proper way to import, but there is no error that tells you that this is a possible issue, so it is very hard to spot. I know it shouldn't be capital but anyone can mistype and there is no indication that this is the issue.

This is what I had: (SO DON'T COPY THIS CODE, IT WON'T WORK)

import Vue from 'Vue';
import Vuex from 'Vuex';

Correct Way:

import Vue from 'vue';
import Vuex from 'vuex'; //<-- not Sentence case from '[V]uex' but '[v]uex'

Such a dumb error, but I'm sure others have had the same issue, so hopefully this helps.

Upvotes: 0

isuryanarayanan
isuryanarayanan

Reputation: 31

do this

let that = this;

and use that to do the dispatching

that.$store.dispatch(action)

Upvotes: 2

Rahman
Rahman

Reputation: 552

It is unclear why it is not defined, and why it works when using modules, but not without modules. It could have something to do with the build process / transpiler. If use is actually a static method of the Vue class, then $store would be shared across all instances by installing the plugin via use, but this is not the case. It looks like Vue should be a class exported by the Vue module, but it seems to behave like an instance.

What I've found to work is one of the following (preferred being #3):

1. Call Vue.use wherever Vuex is used

src
├── App.vue
├── main.js <- Vue.use(Vuex)
├── router 
│   └── index.js <- Vue.use(Vuex)
└── store
    ├── myModule.js
    └── index.js <- Vue.use(Vuex)
// store/index.js
import Vue from "vue"; // This module's Vue is not the same instance as that referenced in main.js
import Vuex from "vuex";
import myModule from "./myModule.js";

// Required before creating new store instance
Vue.use(Vuex);

export const store = new Vuex.Store(myModule);
// main.js
import Vue from "vue"; // this module's Vue is different from store/index.js' Vue
import Vuex from "vuex";
import app from "./App";
import router from "./router/index.js";
import { store } from "./store/index.js";

// this part is essential despite already calling Vue.use in store/index.js
Vue.use(Vuex);
// Or, see alternative below

new Vue({
    ...,
    router,
    store,
    components: { app }
});

2. Set $store for all instances of Vue

(As pointed out by yukashima huksay)

src
├── App.vue
├── main.js <- Vue.prototype.$store = store
├── router 
│   └── index.js
└── store
    ├── myModule.js
    └── index.js
Vue.prototype.$store = store;

3. Import global Vue instance into store and main

src
├── App.vue
├── global.js
├── main.js <- Vue.use(Vuex)
├── router
│   └── index.js
└── store
    ├── myModule.js
    └── index.js
// global.js
import vue from "vue";
export const Vue = vue;

// or if you're not using babel, you can use real javascript…,
export { Vue } from "vue";
// store/index.js
import { Vue } from "../global.js";
import vuex from "vuex";
import myModule from "./myModule.js";

Vue.use(vuex);

export const store = new vuex.Store(myModule);
// main.js
import { Vue } from "./global.js";
import store from "./store/index.js";
import router from "./router/index.js";
import app from "./App";

new Vue({
    ...,
    router,
    store,
    components: { app }
});

Upvotes: 9

yukashima huksay
yukashima huksay

Reputation: 6238

I had the same problem, I even asked my own Stackoverflow question for it no no avail until I finally found the solution myself which I've posted here

The content of the posted answer is as follows:

I was struggling with this problem for two days until I finally found the solution.

My problem was because I was using $navigateTo without specifying the frame, I was navigating the whole component. I discovered that store was only bound to the first component that is passed to the render function in main.js

Here is how my main.js looked:

new Vue({
  store,
  render: h => h('frame', [h(store.state.is_logged_in ? Main : Login)]),
  created() {
    this.$store.commit('setNav', this.$navigateTo);
    if (this.$store.state.is_logged_in) {
      this.$store.dispatch('init');
    }
  },
}).$start();

I discovered that If is_logged_in was true this.$store, or mapActions etc only worked in Main and it's child components, otherwise it would only work in Login and it's child components. Then I was reading This and saw the following code in the store code of the example:

import Vue from 'nativescript-vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({...});
Vue.prototype.$store = store;
module.exports = store;

So I added the line Vue.prototype.$store = store; to my own store definition and finally my problem was solved. This really gave me such a hard time. Hope I can save somebody.

Upvotes: 0

Konrad Kalemba
Konrad Kalemba

Reputation: 1229

That's because you probably didn't add store option to the root instance of Vue. By providing it you will be able to access store from all of root's child components. Therefore your root instance should look like this:

import store from './store'

const app = new Vue({
  /* .. other properties .. */
  store
})

Now you can freely use this.$store within your components.

Upvotes: 45

Mukundhan
Mukundhan

Reputation: 3457

If we are going to follow app structure suggested by vueX.

Store will be injected automatically

https://vuex.vuejs.org/guide/structure.html

Upvotes: 0

Be Kind
Be Kind

Reputation: 5172

My guess would be either not initialized store, or wrong this context.

For the purposes of debugging I'd try using mapActions helper vuex.vuejs.org:

import { mapActions } from 'vuex'

export default {
  methods: {
    ...mapActions({
      add: 'increment' // map `this.add()` to `this.$store.dispatch('increment')`
    })

    // ...
  }

  // ...
}

Upvotes: 0

niclas_4
niclas_4

Reputation: 3674

On a other note

firebase.auth.onAuthStateChanged(user => {
  if(!app){
    /* eslint-disable no-new */
    app = new Vue({
      el: '#app',
      router,
      store,
      components: { App },
      template: '<App/>'
    })
  }
});

I bet you have this piece of code out of some tutorial because i was in that situation too but it will make your applicaton a lot slower on page reload because it will rerender everything everytime again.

I suggest you to use the onAuthStateChanged Event in your router (yes thats possible) and then check there for possible route blocks or not, will be a lot faster.

Im not home for code examples but i can send some later today if you want.

Upvotes: 0

Tom Harpaz
Tom Harpaz

Reputation: 489

So i've tried all of your solutions, and nothing seems to work out for me. I started to suspect that the problem is related to the fact that deleteUser.vue is not a direct child of App.vue. Anyone I did eventually imported store directly to the component:

import store from '../../store.js'

It solved the problem. I wonder if anyone know a more efficient way to solve this problem. Thank you for your assistance!

Upvotes: 1

Related Questions