I'm going crazy, I have a working api that sends data, I connected it to a VueJS app and it was working fine. I'm trying to implement Vuex and I'm stuck. Here's my store.js file
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios'
Vue.use(Vuex);
const state = {
message: "I am groot",
articles: []
}
const getters = {
getArticles: (state) => {
return state.articles;
}
}
const actions = {
getArticles: ({ commit }, data) => {
axios.get('/articles').then( (articles) => {
commit('GET_ARTICLES', articles);
console.log(articles); // Trying to debug
}, (err) => {
console.log(err);
})
}
}
const mutations = {
GET_ARTICLES: (state, {list}) => {
state.articles = list;
}
}
const store = new Vuex.Store({
state,
getters,
mutations,
actions,
mutations
});
console.log(store.state.articles); // this lines works but data is empty
export default store
The console.log within axios call doesn't run and store.state.articles is empty. I must be missing something. I'm just trying to console the articles data on page load...
Please help, I'm near insanity :)
Component :
<template>
<div class="container">
<h1>Test component yo !</h1>
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
name: 'Test',
computed: {
message() {
return this.$store.state.message
}
},
mounted: () => {
this.$store.dispatch('getArticles')
}
}
</script>
App.js :
import Vue from 'vue';
import ArticlesViewer from './articles_viewer.vue';
import UserArticles from './user_articles.vue';
import App from './app.vue'
import store from './store'
new Vue({
el: '#app-container',
store,
render: h => h(App)
})
You define the mounted lifecycle hook of your component using an arrow function.
As per the documentation:
Don’t use arrow functions on an instance property or callback (e.g. vm.$watch('a', newVal => this.myMethod())). As arrow functions are bound to the parent context, this will not be the Vue instance as you’d expect and this.myMethod will be undefined.
You should define it like so:
mounted: function () {
this.$store.dispatch('getArticles');
}
Or, use the ECMAScript 5 shorthand:
mounted() {
this.$store.dispatch('getArticles');
}
Now, your dispatch method will be called correctly, populating your articles array.
Related
Working on a Vuejs application whereby I use Vuex for state management between the components.In Vuex store, I have an action that fetches some data from an API (which works fine) then populate it to the state (via a mutation). Next, I pass the updated state to the component using getters.
The problem is there is a problem populating data to the state from the action. In the DOM I have tried fetching via computed property or using the getter but get empty string
Vuex Store
const getDefaultState = () => {
return {
clientDeposit: ''
}
}
//state
const state = getDefaultState();
//getters
const getters = {
getDeposit: (state) => state.clientDeposit
}
//actions
const actions = {
fetchClients({ commit}) {
const clientId ="23"
axios.post('/api/motor/fetchClients', {
ClientId: clientId,
})
.then((response)=> {
//console.log(response); //returns data
const deposit = response.data;
commit('setIpfDeposit', deposit);
})
}
}
//mutations
const mutations = {
setIpfDeposit: (state, value) => (state.clientDeposit = value)
}
export default {
state,
getters,
actions,
mutations
}
Component
<template>
<div>
<button onclick="fetchClients()">Fetch Clients</button>
Deposit (Via computed property) : {{ fetchDeposit }}
Deposit (from getter) : {{ getDeposit }}
</div>
</template>
<script>
import { mapGetters , mapActions } from "vuex";
import axios from "axios";
export default {
name: "",
data() {
return {
}
},
computed: {
...mapGetters([
"getDeposit"
]),
fetchDeposit(){
return this.getDeposit
},
},
methods:{
...mapActions([
"fetchClients"
])
}
};
</script>
<style scoped>
</style>
You need to fetch the data first.
Import mapActions from vuex
import {mapActions, mapGetters} from 'vuex';
Bring in the fetchClients method in your component's methods object
methods:{
... mapActions(['fetchClients']),
}
Then in your component's created life cycle method call the fetchClients method
created(){
this.fetchClients();
}
In my vuex I have one custom auth claim saved to state as claims(state.claims). I can read this from the state in my vue components just fine and I can also see it in vue devtools.
But when I try to use state.claims to query a firestore collection,
Sometimes I get null(the default value in the state), or an error that I could not replicate saying that is was a "custom object B"
It works the first time loaded but after one refresh it breaks until I switch views back and forth
My guess is that it isn't grabbing the value fast enough for claims but I am not sure
Vuex store
import Vue from "vue";
import Vuex from "vuex";
import { vuexfireMutations, firestoreAction } from 'vuexfire'
import {db} from "#/components/fbInit.js";
import firebase from 'firebase'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
tests: [],
claims: [],
user: null,
isLoggedIn: false,
},
mutations: {
...vuexfireMutations,
setUser: state => {
if (firebase.auth().currentUser) {
state.isLoggedIn = true;
state.user = firebase.auth().currentUser;
state.claims = firebase.auth().currentUser.getIdTokenResult().then((idTokenResult) => {
state.claims = idTokenResult.claims.company
}) .catch((error) => {
// eslint-disable-next-line no-console
console.log(error);
});
}
},
},
actions: {
// GET documents in a collection/subcollection and put them into state
getFire: firestoreAction(({ bindFirestoreRef, state }) => {
//return the promise returned by 'bindFirestoreRef'
return bindFirestoreRef('tests', db.collection(state.claims))
}),
setUser: context => {
context.commit('setUser'); //context.commit to perform mutation
},
}
})
Vue Component
<template>
<div class="pa-9">
<div>
<v-card light>
{{ this.claims}}
</v-card>
<v-card >
{{ this.tests }}
</v-card>
</div>
</div>
</template>
<script>
import {db, functions } from '../components/fbInit'
import { mapState } from 'vuex'
import { mapActions } from 'vuex'
export default {
name: "Claims",
data: () => ({
}),
created() {
this.setUser()
this.getFire()
},
computed: {
...mapState ([
'events','wow','user','isLoggedIn', 'claims','tests'
]),
},
methods: {
...mapActions([ 'setUser', 'getFire', ]),
});
}
},
};
</script>
ERRORS
FirebaseError: Function Firestore.collection() requires its first argument to be of type non-empty string, but it was: a custom B object
[Vue warn]: Error in render: "TypeError: Converting circular structure to JSON
--> starting at object with constructor 'B'
| property 'c' -> object with constructor 'B'
| property 'b' -> object with constructor 'Wb'
--- property 'a' closes the circle"
FirebaseError: Function Firestore.collection() requires its first argument to be of type non-empty string, but it was: null
EDIT Main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
require('dotenv').config();
import vuetify from './plugins/vuetify';
// require('#/components/fb.js')
import firebase from 'firebase/app'
import './components/fbInit'
import * as VueGoogleMaps from 'vue2-google-maps';
Vue.config.productionTip = false
/* eslint-disable */
let app;
firebase.auth().onAuthStateChanged(user => {
if(!app) {
app = new Vue({
router,
vuetify,store,
render: h => h(App)
}).$mount('#app')
}
})
I found some doc . Looking at example there state in firestoreAction(({ bindFirestoreRef, state }) should be after }. So it should be:
firestoreAction(({ bindFirestoreRef }, state )
however I am not sure if it will work without unbindFirestoreRef...
I hope it will help you!
Currently trying to test a vue component that is using the vuex ...mapState method but just by bringing it into the component fails my tests.
This is the current error i'm getting:
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import _Object$defineProperty from "../../core-js/object/define-property";
This is my component I'm trying to test.
SimpleComponent.vue
<template>
<h1> Hello!!!</h1>
</template>
<script>
import mapState from 'vuex';
export default {
computed: {
...mapState(["users"])
}
}
</script>
<style>
</style>
This is my store but simplified for testing purposes.
store.js
import Vue from "vue";
import Vuex from "vuex";
const fb = require("./firebaseConfig.js");
Vue.use(Vuex);
fb.auth.onAuthStateChanged(user => {
if (user) {
store.commit('setCurrentUser', user)
// realtime updates from our posts collection
fb.usersCollection.onSnapshot(querySnapshot => {
let userArray = []
querySnapshot.forEach(doc => {
let user = doc.data()
user.id = doc.id
userArray.push(user)
})
store.commit('setUsers', userArray)
})
}
})
export const store = new Vuex.Store({
state: {
users:[],
},
actions: {
clearData({ commit }) {
commit('setUsers', null);
},
},
mutations: {
setUsers(state, val){
state.users = val
},
},
});
And here is my test
SimpleComponentTest.spec.js
import { shallowMount, createLocalVue } from '#vue/test-utils';
import SimpleStore from '../../src/components/SimpleComponent.vue';
import Vuex from 'vuex';
const sinon = require('sinon');
const localVue = createLocalVue();
localVue.use(Vuex);
describe('SimpleComponent.vue', () => {
it('Is Vue Instance', () => {
const wp = shallowMount(SimpleComponent, { computed: { users: () => 'someValue' }, localVue });
expect(wp.isVueInstance()).toBe(true);
});
});
Im using Jest with Sinon for testing. I am a little confused on the proper way to set up the store in my test but this is one way I found when looking online.
The code base is a little bigger than what I am showing you but that is because after running into errors for hours I figured I needed the simplest piece of code to test that uses the ...mapState method and build it back up from there.
Any help would be greatly appreciated :)
Goal: To get a simple test to pass that tests a component that uses ...mapState([]) from vuex
Why am I getting Vue is not defined as an error here:
export default {
state: {
projects: {
loading: true,
failed: false,
lookups: [],
selectedId: 0
}
},
mutations: {
loadingProjectLookups (state, payload) {
state.projects.loading = true;
state.projects.failed = false;
}
},
actions: {
loadProjectLookups (context) {
return new Promise((resolve) => {
// VUE NOT DEFINED HERE:
Vue.http.get('https://my-domain.com/api/projects').then((response) => {
context.commit('updateProjectLookups', response.data);
resolve();
},
response => {
context.commit('failedProjectLookups');
resolve();
});
});
}
}
}
This is my vue config:
'use strict';
import Vue from 'vue';
import Vuex from 'vuex';
var VueResource = require('vue-resource');
/* plugins */
Vue.use(Vuex);
Vue.use(VueResource);
/* stores */
import importPageStore from './apps/import/import-page-store';
/* risk notification import */
import ImportApp from './apps/import/import-app.vue';
if (document.querySelector("#import-app")) {
var store = new Vuex.Store(importPageStore);
new Vue({
el: '#import-app',
store,
render: h => h(ImportApp)
});
}
My understanding is that Vue is defined globally and I cannot see why it is not defined. If I add import Vue from 'vue' to my store then I get a message that http is not defined. So I need to work out why Vue appears not to be available globally as I shouldn't have to do this.
I am using webpack to build my vue components. I have other pages rendered using this methodology and they work just fine. But this one does not? I am honestly stumped as to why as I cannot see any differences. The page renders and works. I can see that Vue is working. How can it be undefined?
In a component, you can use this.$http, however, in your store you will need to import Vue every time.
What you can do, is create a service folder and import Vue there. Then just reference your service in the store file.
There's an example here https://github.com/vuejs/vuex/issues/85
Which suggests something like this:
/services/auth.js
import Vue from 'vue'
export default {
authenticate(request) {
return Vue.http.post('auth/authenticate', request)
.then((response) => Promise.resolve(response.data))
.catch((error) => Promise.reject(error));
},
// other methods
}
In your store file:
import { AUTHENTICATE, AUTHENTICATE_FAILURE } from '../mutation-types'
import authService from '../../services/auth'
export const authenticate = (store, request) => {
return authService.authenticate(request)
.then((response) => store.dispatch(AUTHENTICATE, response))
.catch((error) => store.dispatch(AUTHENTICATE_FAILURE, error));
}
// other actions
This is how VueResource extends Vue prototype.
Object.defineProperties(Vue.prototype, {
// [...]
$http: {
get() {
return options(Vue.http, this, this.$options.http);
}
},
// [...]
});
}
VueResource handles the promise itself. Thus, you don't need to wrap the requests in promises. You can use Promise.all() later. But I don't see multiple requests so you just use the get request.
Reference: Using promise in vue-resource
I hope, this would solve your issue with that error.
I'm a little bit confused with vuex store component.
How should I obtain state of another module?
I tried a different ways to get data from store and always got Observer object. What is the correct way to knock knock to observer?
If I try to get anything from this object directly, like rootState.user.someVariable then I got undefined response.
Don't have a problem getting state from components.
Edit. Add code
User module
import * as Constants from './../../constants/constants'
import * as types from '../mutation-types'
import axios from 'axios'
const state = { user: [] }
const getters = {
getUser: state => state.user
}
const actions = {
getUserAction ({commit}) {
axios({method: 'GET', 'url': Constants.API_SERVER + 'site/user'})
.then(result => {
let data = result.data
commit(types.GET_USER, {data})
}, error => {
commit(types.GET_USER, {})
console.log(error.toString())
})
}
}
const mutations = {
[types.GET_USER] (state, {data}) {
state.user = data
}
}
export default { state, getters, actions, mutations }
Mutatinos
export const GET_LANGS = 'GET_LANGS'
export const GET_USER = 'GET_USER'
Store
import Vuex from 'vuex'
import Vue from 'vue'
import user from './modules/user'
import lang from './modules/lang'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
user,
lang
}
})
Main app
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store/index'
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
Lang module, here is the place where I'm trying get store
import * as types from '../mutation-types'
import {axiosget} from '../../api/api'
const state = { langList: [] }
const getters = {
getLangs: state => state.langList
}
const actions = {
// this two action give me similar result
getLangsAction (context) {
axiosget('lang') // described below
},
getAnotherLangsAction (context) {
console.log(context.rootState.user) <----get Observer object
}
}
const mutations = {
[types.GET_LANGS] (state, {data}) {
state.langList = data
}
}
export default { state, getters, actions, mutations }
axiosget action, api module
import * as Constants from './../constants/constants'
import store from '../store/index'
import axios from 'axios'
export const axiosget = function (apiUrl, actionSuccess, actionError) {
console.debug(store.state.user) // <----get Observer object, previously described
// should append user token to axios url, located at store.state.user.access_token.token
axios({method: 'GET', 'url': Constants.API_URL + apiUrl
+ '?access_token=' + store.state.user.access_token.token})
.then(result => {
let data = result.data
// todo implement this
// }
}, error => {
if (actionError && actionError === 'function') {
// implement this
}
})
}
Component, that call dispatcher. If i get state via mapGetters in computed properties - there is no problems
<template>
<div>
{{user.access_token.token}}
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'ArticlesList',
computed: mapGetters({
user: 'getUser'
}),
created () {
this.$store.dispatch('getLangsAction')
this.$store.dispatch('getAnotherLangsAction')
}
}
</script>
What I'm trying to do in this code - get user access token in main site (after login) and all further manipulations with data will be produced via api host.
Let's say you want to fetch state an attribute userId from object userDetails in Vuex store module user.js.
userDetails:{
userId: 1,
username: "Anything"
}
You can access it in following way in action
authenticateUser(vuexContext, details) {
userId = vuexContext.rootState.user.userDetails.userId;
}
Note: After rootState and before file name user, add the path to the store module file if it is inside nested folders.