vuejs / vuex

🗃️ Centralized State Management for Vue.js.
https://vuex.vuejs.org
MIT License
28.42k stars 9.58k forks source link

Uncaught TypeError: Cannot read property '$watch' of undefined #287

Closed nivv closed 8 years ago

nivv commented 8 years ago

So I did a fresh reinstall of my node modules and now I get this error. I'm using webpack.

Has anyone had the same issue?

Screenshots of the errors:

skarmklipp 2016-08-22 10 27 35 skarmklipp 2016-08-22 10 27 41

My package.json:

{
  "private": true,
  "scripts": {
    "prod": "gulp --production",
    "dev": "gulp watch"
  },
  "devDependencies": {
    "bootstrap-sass": "^3.3.7",
    "gulp": "^3.9.1",
    "laravel-elixir": "^6.0.0-9",
    "laravel-elixir-vue": "^0.1.4",
    "laravel-elixir-webpack-official": "^1.0.2",
    "lodash": "^4.14.0"
  },
  "dependencies": {
    "bootstrap-sass": "^3.3.7",
    "chart.js": "^2.2.1",
    "clipboard": "^1.5.12",
    "dragula": "^3.7.1",
    "dropzone": "^4.3.0",
    "filesize": "^3.3.0",
    "font-awesome": "^4.6.3",
    "jwt-decode": "^2.1.0",
    "keen-ui": "^0.8.9",
    "ladda": "^1.0.0",
    "moment": "^2.14.1",
    "numeral": "^1.5.3",
    "selectize": "^0.12.2",
    "simplemde": "^1.11.2",
    "vue": "^1.0.26",
    "vue-resource": "^0.9.3",
    "vue-router": "^0.7.13",
    "vuex": "^1.0.0-rc.2",
    "jquery": "^3.1.0"
  }
}

My store.js

import Vue from 'vue'
import Vuex from 'vuex'

// Make vue aware of Vuex
Vue.use(Vuex)

// Create an object to hold the initial state when
// the app starts up
const state = {
    user: {
        authenticated: false
    }
}

export default new Vuex.Store({
  state,
  mutations
})
ktsn commented 8 years ago

Hi, thanks for reporting the issue! Can you provide a small reproduction on an online JS playground like jsfiddle or webpackbin? It helps us to catch the problem correctly.

nivv commented 8 years ago

@ktsn Sorry, but I can't reproduce it, it's super weird.

Full store.js file:

import Vue from 'vue'
import Vuex from 'vuex'

// Make vue aware of Vuex
Vue.use(Vuex)
// Create an object to hold the initial state when
// the app starts up
const state = {
    user: {
        authenticated: false
    },
    snackbarMessage: {},
    images: [],
    files: [],
    imageList: [],
    fileList: [],
    widgetImageList: [],
    widgetFileList: [],
    languages: [{
        id: 1,
        name: 'Svenska',
        locale: 'sv',
        text: 'Svenska',
        value: 1
    }, {
        id: 2,
        name: 'English',
        locale: 'en',
        text: 'English',
        value: 2
    }],
    token: false,
}

// Create an object storing various mutations. We will write the mutation
const mutations = {

    SET_USER(state, user) {
        state.user = user;
    },

    SET_IMAGE_LIST(state, data) {
        state.imageList = data;
    },

    SET_WIDGET_IMAGE_LIST(state, data) {
        state.widgetImageList = data;
    },

    SET_WIDGET_FILE_LIST(state, data) {
        state.widgetFileList = data;
    },

    SET_IMAGES(state, data) {
        state.images = data;
    },

    UPDATE_IMAGE(state, image) {
        for (var i = 0; i < state.images.length; i++) {
            if(state.images[i].uuid == image.uuid) {
                state.images[i].name = image.name
            }
        }

        //Update all items with the updated id
        for (var i = 0; i < state.imageList.length; i++) {
            if(state.imageList[i].uuid == image.uuid) {
                state.imageList[i].name = image.name
            }
        }
        // Update the images in the widget list as well
        for (var i = 0; i < state.widgetImageList.length; i++) {
            if(state.widgetImageList[i].uuid == image.uuid) {
                state.widgetImageList[i].name = image.name
            }
        }
    },

    PUSH_IMAGE(state, image) {
        state.images.push(image);
    },

    PUSH_IMAGE_LIST(state, images) {
        state.imageList[image.uuid] = image;
    },

    SET_FILES(state, data) {
        state.files = data;
    },

    SET_FILE_LIST(state, data) {
        state.fileList = data;
    },

    PUSH_FILE(state, file) {
        state.files.push(file);
    },

    UPDATE_FILE(state, file) {
        for (var i = 0; i < state.files.length; i++) {
            if(state.files[i].uuid == file.uuid) {
                state.files[i].name = file.name;
                state.files[i].readable_name = file.readable_name;
            }
        }

        //Update all items with the updated id
        for (var i = 0; i < state.fileList.length; i++) {
            if(state.fileList[i].uuid == file.uuid) {
                state.fileList[i].name = file.name
                state.fileList[i].readable_name = file.readable_name;
            }
        }

        // Update widget file list
        for (var i = 0; i < state.widgetFileList.length; i++) {
            if(state.widgetFileList[i].uuid == file.uuid) {
                state.widgetFileList[i].name = file.name
                state.widgetFileList[i].readable_name = file.readable_name;
            }
        }
    },

    SET_SNACKBAR_MESSAGE(state, message) {
        state.snackbarMessage = message;
    },

    SET_TOKEN(state, token) {
        state.token = token;
    },

    SET_LANGUAGES(state, data) {
        state.languages = data;
    }
}

// Combine the initial state and the mutations to create a Vuex store.
// This store can be linked to our app.
export default new Vuex.Store({
    state,
    mutations
})

App.vue

<template>
    <div id="wrapper"
        class="sidebar-expanded">
        <ui-snackbar-container position="right">
        </ui-snackbar-container>
        <nav class="navbar navbar-default navbar-fixed-top">
            <div class="container-fluid">
                <div class="navbar-header">
                    <a href="/admin/pages">
                        <span class="navbar-brand"><img src="/svg/iq-cms-logo.svg"
                                class="navbar-brand-img"
                                alt=""> IQ CMS</span>
                    </a>

                </div>
                <div id="navbar"
                    class="navbar-collapse collapse">
                    <user-controls></user-controls>
                </div>
            </div>
        </nav>
        <sidebar></sidebar>
        <div id="main-content"
            v-bind:class="{ 'is-admin': user.admin }">
            <div id=""
                class="container-fluid container-padding">
                <router-view class="view"
                    transition
                    transition-mode="out-in">
                </router-view>
            </div>
        </div>
    </div>
</template>
<script>
import auth from '../auth';
import store from './../vuex/store';
import Sidebar from './admin/Sidebar.vue';
import UserControls from './admin/UserControls.vue';
import {
    UiSnackbarContainer,
    UiSnackbar
} from 'keen-ui';
export default {
    replace: false,
    name: 'App',
    store: store,
    vuex: {
        getters: {
            token: state => state.token,
            user: state => state.user,
            images: state => state.images,
            files: state => state.files,
            languages: state => state.languages,
            snackbarMessage: state => state.snackbarMessage,
        }
    },
    components: {
        UserControls,
        Sidebar,
        UiSnackbarContainer,
        UiSnackbar
    },
    ready: function() {
        this.fetchLanguages();
    },
    watch: {
        'snackbarMessage': function(message) {
            this.$broadcast('ui-snackbar::create', {
                message: message.message,
                duration: message.duration
            });
        }
    },
    methods: {
        fetchLanguages: function() {
            this.$http.get('/api/v2/languages').then((response) => {
                // handle success
                store.dispatch('SET_LANGUAGES', response.data.data);
            }, (response) => {
                // handle error
            });
        }
    }
}
</script>
ktsn commented 8 years ago

OK, it happens. Thank you for sharing your code. I have found this issue in my environment. I'll look into it :)

ktsn commented 8 years ago

Looks like this is because using both buble-loader and babel-loader in laravel-elixir-webpack-official and laravel-elixir-vue.

To avoid this, we should add webpack.config.js to project root (same directory that package.json is located), and overwrite laravel-elixir's configuration.

  1. To use babel, overwrite the loader for .js files

    module.exports = {
     module: {
       loaders: [
         { test: /\.js$/, loader: 'babel', exclude: /node_modules/ },
         { test: /\.vue$/, loader: 'vue' }
       ]
     }
    }
  2. To use buble, add configuration for vue-loader

    module.exports = {
     vue: {
       loaders: {
         js: 'buble'
       }
     }
    }
nivv commented 8 years ago

@ktsn thanks! How nice of you to debug this for me! 😄 Will try it out and report back!

tripper54 commented 8 years ago

@ktsn thanks for the tip. This is what fixed the problem for me, in gulpfile.js:

Elixir.webpack.mergeConfig({
    module: {
        loaders: [
            { test: /\.js$/, loader: 'babel', exclude: /node_modules/ },
        ]
    }
});

(this is Laravel 5.3)

ktsn commented 8 years ago

I'm closing this issue because it seems to be not about vuex's issue. Feel free to reopen if you find the cause in vuex :smiley:

nivv commented 8 years ago

@ktsn adding a webpack.config.js and putting below into it works:

module.exports = {
  module: {
    loaders: [
      { test: /\.js$/, loader: 'babel', exclude: /node_modules/ },
      { test: /\.vue$/, loader: 'vue' }
    ]
  }
}

Couldn't get Bublé to work though.