I followed these instructions in the Vuex documentation for accessing the Vuex state from my Vue components... but whenever I use this.$store.something in my components, I get TypeError: Cannot read property 'something' of undefined (see screenshot at the bottom of this post).
The documentation says,
By providing the store option to the root instance, the store will be
injected into all child components of the root and will be available
on them as this.$store
...but that functionality does not seem to be working in my application.
Here is my code:
main.js
import Vue from 'vue'
import App from './App'
import axios from 'axios'
import router from './router'
import store from './store'
Vue.config.productionTip = false
axios.defaults.baseURL = 'http://localhost:3000'
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
store.js
import Vue from 'Vue'
import Vuex from 'vuex'
import router from './router'
import axios from 'axios'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
token: null
},
mutations: { // setters (synchronous)
setToken (state, userData) {
state.token = userData.token
},
clearToken (state) {
state.token = null
}
},
actions: { // asynchronous tasks
signup (authData) {
axios.post('/user/signup', {
email: authData.email,
password: authData.password
})
.then(res => {
if (res.status === 201) {
// what happens if signup succeeds?
} else {
// what happens if signup fails?
}
})
.catch(error => console.log(error))
},
setLogoutTimer ({commit}, expiresIn) {
setTimeout(() => {
commit('clearToken')
}, expiresIn * 1000)
},
login ({commit, dispatch}, authData) {
axios.post('/user/login', {
email: authData.email,
password: authData.password
})
.then(res => {
console.log(res)
// set token with timeout
const now = new Date()
const tokenExpiration = new Date(now.getTime() + res.data.expiresIn * 1000)
localStorage.setItem('token', res.data.token)
localStorage.setItem('tokenExpiration', tokenExpiration)
commit('setToken', { token: res.data.token })
dispatch('setLogoutTimer', res.data.expiresIn)
// redirect to dashboard
router.replace('/dashboard')
})
.catch(error => console.log(error))
},
tryAutoLogin ({commit}) {
const token = localStorage.getItem('token')
if (!token) {
return
}
const tokenExpiration = localStorage.getItem('tokenExpiration')
const now = new Date()
if (now >= tokenExpiration) {
return
}
commit('setToken', { token: token })
},
logout ({commit}) {
commit('clearToken')
localStorage.removeItem('token')
localStorage.removeItem('tokenExpiration')
router.replace('/login')
}
},
getters: {
isAuthenticated (state) {
return state.token !== null
}
}
})
App.vue
<template>
<div id="app">
<app-header/>
<router-view/>
</div>
</template>
<script>
import Header from './components/Header.vue'
export default {
name: 'App',
components: {
'app-header': Header
},
created () {
this.$store.dispatch('tryAutoLogin')
}
}
</script>
Header.vue
<template>
<header id="header">
<div class="logo">
<router-link to="/">Home</router-link>
</div>
<nav>
<ul>
<li v-if="!auth">
<router-link to="/signup">Sign Up</router-link>
</li>
<li v-if="!auth">
<router-link to="/login">Login</router-link>
</li>
<li v-if="auth">
<router-link to="/dashboard">Dashboard</router-link>
</li>
<li v-if="auth">
<a #click="onLogout">Logout</a>
</li>
</ul>
</nav>
</header>
</template>
<script>
export default {
computed: {
auth () {
return this.$store.state.token !== null
}
},
methods: {
onLogout () {
this.$store.dispatch('logout')
}
},
watch: {
$route () {
console.log('STORE: ', this.$store.state)
}
}
}
</script>
The errors:
Use Destructuring when importing store into your main.js file.
Change your code from
import Vue from 'vue'
import App from './App'
import axios from 'axios'
import router from './router'
import store from './store'
Vue.config.productionTip = false
axios.defaults.baseURL = 'http://localhost:3000'
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
To
import Vue from 'vue'
import App from './App'
import axios from 'axios'
import router from './router'
import { store } from './store' //Added Destructuring
Vue.config.productionTip = false
axios.defaults.baseURL = 'http://localhost:3000'
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
It worked for me..Hope it works for you as well!
Everything looks good in your code. However in your main.js file change the following code
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
to
new Vue({
el: "#app",
router,
store,
render: h => h(App)
});
Related
I am trying to push data from an api call to my store, but I keep getting errors like commit is undefined and dispatch is undefined.
If I understood documents correctly I can only manipulate state from components by creating mutations? been going round in circles for an hour now and would appreciate some guidance.
in main.js
import Vue from 'vue'
import Vuex from 'vuex'
import App from './App.vue'
import router from './router'
import 'es6-promise/auto'
Vue.config.productionTip = false
new Vue({
router,
render: (h) => h(App),
store: store,
}).$mount('#app')
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
images: [],
},
mutations: {
addImages(state, newImages) {
state.images = []
state.images.push(newImages)
},
},
})
Header.js component:
<template>
<div>
<h1>Nasa Image Search</h1>
<div class="search-container">
<form action="/action_page.php">
<input v-model="searchWord" type="text" placeholder="Search.." name="search" />
<button v-on:click.prevent="search" type="submit">Search</button>
</form>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'Header',
data: () => ({
searchWord: "",
//images: [],
}),
methods: {
search() {
let url
this.searchWord
? url = `https://images-api.nasa.gov/search?q=${this.searchWord}&media_type=image`
: url = `https://images-api.nasa.gov/search?q=latest&media_type=image`
console.log(url) //bug testing
axios
.get(url)
.then(response => {
const items = response.data.collection.items
console.log(items)
this.$store.commit('addImages', items)
console.log(this.$store.state.images)
})
.catch(error => {
console.log(error)
})
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>
Your code is perfectly fine, please create a new file store.js as below and import it in main.js and it will work. In newer version of vuex eg 4.0 we do have createStore function using which we can include store code in main.js file itself.
store.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: { images: [], },
mutations: {
addImages(state, newImages) {
state.images = []
state.images.push(newImages)
},
},
});
main.js
import Vue from "vue";
import App from "./App.vue";
import { store } from "./store";
import router from './router'
import 'es6-promise/auto'
Vue.config.productionTip = false;
new Vue({
router, store,
render: h => h(App)
}).$mount("#app");
in Vue 3 with Vuex 4 -> We can have store code inside main.js as below. doc link https://next.vuex.vuejs.org/guide/#the-simplest-store
import { createStore } from 'vuex'
import { createApp } from 'vue'
const store = createStore({
state () {
return {
}
}
})
const app = createApp({ /* your root component */ })
app.use(store)
Normally I create my Vuex 'store' in a separate file and import it into main.js.
I did a local experiment where I declared 'const store' in main.js AFTER instantiating new Vue() in main.js, as you are doing, and am getting errors.
Try declaring 'const store' BEFORE new Vue().
I am following an upgrade guide on how to go from Vue to Vue3. It shows how to handle it if the app is structured like:
new Vue({
router,
render: h => h(App)
}).$mount("#app");
The problem is that my my app is structured like this:
new Vue({
el: '#app',
data() {
return {
// initialData
};
},
mounted() {
// mounted
},
methods: {
}
}
Where do I place the data, mounted, methods, etc to have it work with the new structure in Vue 3?
You could import h to render the App component and use your usual options :
import {createApp,h} from 'vue'
...
createApp({
data() {
return {
// initialData
};
},
mounted() {
// mounted
},
methods: {
},
render: () => h(App)
})
isn't it the same as just creating an App Component?
const app = createApp(App);
app.mount("#app");
in the App Component
import { defineComponent, onMounted } from "vue";
export default defineComponent({
name: "App",
components: {},
setup() {
const initialData = "";
onMounted(() => {
console.log("mounted");
});
const aMethod = () => {
return null;
};
return {
initialData,
aMethod
};
}
});
</script>
Good day am working on a vue project that uses both vue-router and vuex
In the app whenever login is successful, I store the the JWT token in vuex. However im strugling with the following
1) Dynamically show/hide login/logoff buttons in my navbar
2) Access JWT token in vuex store when the routes changes in router.js
So basically i need access to my vuex store in my router and also how to update my navbar component when my vuex state changes (ie when login is successful)
Below is my code. I'll start with main.js
import Vue from 'vue'
import Router from 'vue-router'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css'
import App from '../app.vue'
import router from '../router.js'
import store from '../store'
Vue.use(Router)
Vue.use(Vuetify)
const vuetify = new Vuetify
document.addEventListener('DOMContentLoaded', () => {
new Vue({
el: '#app',
router,
vuetify,
store,
render: h => h(App)
})
})
Then router.js
import Router from 'vue-router'
import Index from './views/index.vue'
import Appointments from './views/appointments.vue'
import CreateAppointment from './views/createAppointment.vue'
import Login from './views/login.vue'
import store from './store'
const router = new Router({
routes: [
{
path: '/',
name: 'root',
component: Index
},
{
path: '/login',
name: 'login',
component: Login
},
{
path: '/index',
name: 'index',
component: Index
},
{
path: '/appointments',
name: 'appointments',
component: Appointments,
props: true,
meta: {
requiresAuth: true
}
},
{
path: '/create-appointment',
name: 'create-appointment',
component: CreateAppointment,
meta: {
requiresAuth: true
}
}
]
})
router.beforeEach((to, from, next) => {
if(to.matched.some(record => record.meta.requiresAuth)) {
if (store.getters.isLoggedIn) {
next()
return
}
next('/login')
} else {
next()
}
})
export default router
Then comes store which is index.js inside of store folder
import Vue from 'vue'
import Vuex from 'vuex'
import * as patient from './modules/patient.js'
import * as appointment from './modules/appointment.js'
import * as user from './modules/user.js'
import * as notification from './modules/notification.js'
Vue.use(Vuex)
export default new Vuex.Store({
state: {},
modules: {
user,
patient,
appointment,
notification
}
})
And lastly is my nabar.vue component which is nested in App.vue. Ps i have taken out navigation drawer and bottom navigation to lessen the code.
<template>
<nav>
<v-app-bar app dark>
<v-btn text rounded dark v-on:click='showDrawer = !showDrawer'>
<v-icon class='grey--text' left>menu</v-icon>
</v-btn>
<v-toolbar-title d-sm-none>
<span class='font-weight-light'>SASSA</span>
</v-toolbar-title>
<v-spacer></v-spacer>
<!-- Log in button -->
<v-btn text rounded dark v-on:click='login' v-show='!isLoggedIn'>
<span>Login</span>
<v-icon class='grey--text' right>arrow_forward</v-icon>
</v-btn>
<!-- Log out button -->
<v-btn text rounded dark v-on:click='logout' v-show='isLoggedIn'>
<span>Logout</span>
<v-icon class='grey--text' right>exit_to_app</v-icon>
</v-btn>
</v-app-bar>
</nav>
</template>
<script>
import { mapState } from 'vuex'
export default {
data(){
return {
activeBtn: 1,
showDrawer: false,
links: [
{ title: 'Appointments', icon: 'calendar_today', link: '/appointments'},
{ title: 'New Appointment', icon: 'event', link: '/create-appointment'}
]
}
},
computed: {
isLoggedIn() {
var result = (this.$store.getters['isLoggedIn'] == "true")
return result
},
...mapState(['user'])
},
methods: {
logout() {
this.$store.dispatch('user/logout')
.then(() => {
this.$router.push('/login')
})
},
login() {
this.$router.push('/login')
}
}
}
</script>
This is where i try to access store in router
if (store.getters.isLoggedIn) {
Any help would be highly appreciated
Below is the user.js module
import AxiosService from '../services/AxiosService.js'
export const namespaced = true
export const state = {
status: '',
token: '',
user: {}
}
export const mutations = {
AUTH_SUCCESS(state, payload){
state.status = 'logged_on'
state.token = payload.token
state.user = payload.user
},
LOGG_OFF(state){
state.status = 'logged_off'
state.token = ''
state.user = {}
}
}
export const actions = {
login({commit, dispatch}, user){
return AxiosService.logon(user)
.then(response => {
const token = response.data.auth_token
const user = response.data.user
localStorage.setItem('token',token)
commit('AUTH_SUCCESS', {token, user})
})
.catch(error => {
const notification = {
type: 'error',
message: 'Invalid Credentials !!!'
}
dispatch('notification/add', notification, { root: true})
throw error
})
},
logout({commit}){
localStorage.setItem('token','')
commit('LOGG_OFF')
}
}
export const getters = {
isLoggedIn: state => !!state.token,
authStatus: state => state.status,
getUser: state => state.user
}
Hey all i managed to fix it.
Problem was because my modules are namepsaced i have to access like this
store.getters['user/isLoggedIn']
Instead of
store.user.isLoggedIn
Thanks again. However i still have the issue of the navbar not updating
//this is my signupform.js where i have an object which have my form keys
import Datepicker from 'vuejs-datepicker'
import store from '../../store';
export default {
name: 'Signupform',
components: {
Datepicker,store
},
data() {
return {
enter_details:
{
username: '',
email: '',
contactNumber: '',
firstName: '',
lastName:'',
dob: '',
password: '',
repeat_password: ''
}
}
},
methods:{
addtoAPI() {
this.$store.dispatch('addtoapi',this.enter_details)
}
}
};
//this is my store's action
import vuex from 'vuex';
import axios from 'axios'
vue.use(vuex);
const store = new vuex.Store({
actions: {
addtoapi: ({commit}, enter_details) => {
let newuser = {
username: enter_details.username,
email: enter_details.email,
contactNumber: enter_details.contactNumber,
firstName: enter_details.firstName,
lastName: enter_details.lastName,
dob: enter_details.dob,
password: enter_details.password,
repeat_password: enter_details.repeat_password,
}
console.log(newuser);
axios.post('https://dev-api.mysc.io/int/api/v1', newuser)
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
})
}
}
});
//now i am getting an error i.e
Signupform.js?22e4:28 Uncaught TypeError: this.$store.dispatch is not a function
at VueComponent.addtoAPI (Signupform.js?22e4:28)
at boundFn (vue.esm.js?efeb:190)
at invoker (vue.esm.js?efeb:2004)
at HTMLButtonElement.fn._withTask.fn._withTask
i am also getting one more error that when i try to see my store on vue on my browser it shows that "no vuex store"
please help me to resolve this error because i have alreaady
//this is my main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
Vue.config.productionTip = false
/* eslint-disable no-new */
export const bus = new Vue();
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
In your store.js write this:
export default new Vuex.Store({
//
});
instead of
export default({
//
});
UPD: demo
And you don't need to include store as a component:
// signupform.js file ...
components: {
Datepicker,
store // <--- this is unnessesary
},
const store = new Vuex.Store({
actions: {
theAction() {
alert('Action fired');
},
},
});
const app = new Vue({
el: "#app",
store,
methods: {
fireAction() {
this.$store.dispatch('theAction')
},
},
})
<script src="https://unpkg.com/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.js"></script>
<div id="app">
<button #click="fireAction">Press me</button>
</div>
I'm building an app following guide https://ssr.vuejs.org/en/data.html.
So i have structure:
server.js
const express = require('express');
const server = express();
const fs = require('fs');
const path = require('path');
const bundle = require('./dist/server.bundle.js');
const renderer = require('vue-server-renderer').createRenderer({
template: fs.readFileSync('./index.html', 'utf-8')
});
server.get('*', (req, res) => {
bundle.default({url: req.url}).then((app) => {
const context = {
title: app.$options.router.history.current.meta.title
};
renderer.renderToString(app, context, function (err, html) {
console.log(html)
if (err) {
if (err.code === 404) {
res.status(404).end('Page not found')
} else {
res.status(500).end('Internal Server Error')
}
} else if (context.title === '404') {
res.status(404).end(html)
} else {
res.end(html)
}
});
}, (err) => {
res.status(404).end('Page not found')
});
});
server.listen(8080);
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
import axios from 'axios';
export function createStore() {
return new Vuex.Store({
state: {
articles: [
]
},
actions: {
fetchArticlesList({commit}, params) {
return axios({
method: 'post',
url: 'http://test.local/api/get-articles',
data: {
start: params.start,
limit: params.limit,
language: params.language
}
})
.then((res) => {
commit('setArticles', res.data.articles);
});
},
},
mutations: {
setArticles(state, articles) {
state.articles = articles;
}
}
})
}
router.js
import BlogEn from '../components/pages/BlogEn.vue';
import Vue from 'vue';
import Router from 'vue-router';
export function createRouter() {
return new Router({
mode: 'history',
routes: [
{
path: '/en/blog',
name: 'blogEn',
component: BlogEn,
meta: {
title: 'Blog',
language: 'en'
}
},
});
}
main.js
import Vue from 'vue'
import App from './App.vue'
import {createRouter} from './router/router.js'
import {createStore} from './store/store.js'
import {sync} from 'vuex-router-sync'
export function createApp() {
const router = createRouter();
const store = createStore();
sync(store, router);
const app = new Vue({
router,
store,
render: h => h(App)
});
return {app, router, store};
}
entry-server.js
import {createApp} from './main.js';
export default context => {
return new Promise((resolve, reject) => {
const { app, router, store } = createApp()
router.push(context.url)
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()
if (!matchedComponents.length) {
return reject({ code: 404 })
}
Promise.all(matchedComponents.map(Component => {
// This code not from manual because i want load this in my content-component
if (Component.components['content-component'].asyncData) {
return Component.components['content-component'].asyncData({
store,
route: router.currentRoute
})
}
// This code from manual
// if (Component.asyncData) {
// return Component.asyncData({
// store,
// route: router.currentRoute
// })
// }
})).then(() => {
context.state = store.state
resolve(app)
}).catch(reject)
}, reject)
})
}
entry-client.js
import Vue from 'vue'
import {createApp} from './main.js';
const {app, router, store} = createApp();
if (window.__INITIAL_STATE__) {
store.replaceState(window.__INITIAL_STATE__)
}
router.onReady(() => {
router.beforeResolve((to, from, next) => {
const matched = router.getMatchedComponents(to)
const prevMatched = router.getMatchedComponents(from)
let diffed = false
const activated = matched.filter((c, i) => {
return diffed || (diffed = (prevMatched[i] !== c))
})
if (!activated.length) {
return next()
}
Promise.all(activated.map(c => {
if (c.asyncData) {
return c.asyncData({ store, route: to })
}
})).then(() => {
next()
}).catch(next)
})
app.$mount('#app')
});
Components
BlogEn.vue
<template>
<div>
<header-component></header-component>
<div class="content" id="content">
<content-component></content-component>
<div class="buffer"></div>
</div>
<footer-component></footer-component>
</div>
</template>
<script>
import Header from '../blanks/Header.vue';
import Content from '../pages/content/blog/Content.vue';
import Footer from '../blanks/Footer.vue';
export default {
data() {
return {
};
},
components: {
'header-component': Header,
'breadcrumbs-component' : Breadcrumbs,
'content-component' : Content,
'footer-component': Footer
},
};
</script>
Content.vue
<template>
<section class="blog">
<div v-for="item in articles">
<p>{{ item.title }}</p>
</div>
</section>
</template>
<script>
export default {
data() {
let obj = {
};
return obj;
},
asyncData({store, route}) {
let params = {
start: 0,
limit: 2,
language: 'ru'
};
return store.dispatch('fetchArticlesList', params);
},
computed: {
articles () {
return this.$store.state.articles;
}
}
};
</script>
When i load page /en/blog
My DOM in browser looks like
<div id="app">
<div id="content" class="content">
<!-- There is should be loop content -->
<div class="buffer"></div>
</div>
<footer></footer>
</div>
But! When i look at source code page and html that server sends to me its OK.
<div id="app">
<div id="content" class="content">
<section class="blog">
<div><p>Article Title</p></div>
<div><p>Article Title 2</p></div>
</section>
<div class="buffer"></div>
</div>
<footer></footer>
</div>
Thats not all. I have other pages in my app that i dont show here. When i move at any page and go to "/en/blog" after that DOM is ok.
What's wrong here?