NuxtJS + Vuex — datas in the store - javascript

Using NuxtJS (a VueJS framework), I’m trying to get a bunch of datas from a REST API in a layout template (which can’t use the classic fech() or asyncData() methods).
So I'm using vuex and the nuxtServerInit() action.
This way, I should be able to gather all the datas directly during the load of the app, regardless of the current page.
But I can’t get it to work.
Here’s my map.js file for the store:
import axios from 'axios'
const api = 'http://rest.api.localhost/spots'
export const state = () => ({
markers: null
})
export const mutations = {
init (state) {
axios.get(api)
.then((res) => {
state.markers = res.data
})
}
}
export const actions = {
init ({ commit }) {
commit('init')
}
}
And the index.js (that can fire the nuxtServerInit()):
export const state = () => {}
export const mutations = {}
export const actions = {
nuxtServerInit ({ commit }) {
// ??
console.log('test')
}
}
But I can’t get it to work. The doc says:
If you are using the Modules mode of the Vuex store, only the primary module (in store/index.js) will receive this action. You'll need to chain your module actions from there.
But I don’t know how I shall do this. How do I call an action defined in another module/file?
I tried to copy various example, but never got them to work ; this is the best I could come up with.
What did I missed? If needed, here’s the repo and the store folder
Thanks!

I ran into the same problem, a few weeks ago, and here is how I solved it:
======== CLASSIC MODE =========
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import auth from './modules/auth'
import auth from './modules/base'
Vue.use(Vuex)
export default () => {
return new Vuex.Store({
actions: {
nuxtServerInit ({ commit }, { req }) {
if (req.session.user && req.session.token) {
commit('auth/SET_USER', req.session.user)
commit('auth/SET_TOKEN', req.session.token)
}
}
},
modules: {
auth,
base
}
})
}
store/modules/auth.js
const state = () => ({
user: null,
token: null
})
const getters = {
getToken (state) {
return state.token
},
getUser (state) {
return state.user
}
}
const mutations = {
SET_USER (state, user) {
state.user = user
},
SET_TOKEN (state, token) {
state.token = token
}
}
const actions = {
async register ({ commit }, { name, slug, email, password }) {
try {
const { data } = await this.$axios.post('/users', { name, slug, email, password })
commit('SET_USER', data)
} catch (err) {
commit('base/SET_ERROR', err.response.data.message, { root: true })
throw err
}
},
/* ... */
}
export default {
namespaced: true,
state,
getters,
mutations,
actions
}
Please notice the lines commit('base/SET_ERROR', err.response.data.message, { root: true }), which calls the mutation in another module (called base). And the namespaced: true option, which is required for this to work.
To learn more about namespacing in vuex modules, please refer to the documentation: https://vuex.vuejs.org/en/modules.html
======== MODULES MODE =========
The new 'modules mode' makes this much easier. You can have all the files in one folder and 'namespaced = true' is not required anymore.
Here is how the above files look in modules mode:
store/index.js
export const state = () => ({})
export const actions = {
async nuxtServerInit ({ commit }, { req }) {
if (req.session.user && req.session.token) {
commit('auth/SET_USER', req.session.user)
commit('auth/SET_TOKEN', req.session.token)
}
}
}
store/auth.js
const state = () => ({
user: null,
token: null
})
const getters = {
getUser (state) {
return state.user
},
getToken (state) {
return state.token
}
}
const mutations = {
SET_USER (state, user) {
state.user = user
},
SET_TOKEN (state, token) {
state.token = token
}
}
const actions = {
async register ({ commit }, { name, slug, email, password }) {
try {
const { data } = await this.$axios.post('/users', { name, slug, email, password })
commit('SET_USER', data)
} catch (err) {
commit('base/SET_ERROR', err.response.data.message, { root: true })
throw err
}
}
}
export default {
state,
getters,
mutations,
actions
}
To learn more about modules mode in nuxtjs, please refer to the documentation:
https://nuxtjs.org/guide/vuex-store/#modules-mode

Related

Redux-toolkit + Signalr

What is the best way to update the state with signalr? At this moment I dispatching data inside on method callback and accessing it using useSelector inside other component.
I wonder if this is the optimal approach.
useEffect(()=> {
const connection = $.hubConnection("http://x.x.x.x/signalr", { useDefaultPath: false })
const proxy = connection.createHubProxy("exampleHub")
proxy.on("action", function(data) {
dispatch(setStatus(data))
}
})
Slice.js:
export const monitorSlice = createSlice({
name: "example",
initialState: {
status: {},
},
reducers: {
setStatus(state, {payload}) => {
state.status = payload
}
}
});
Your approach will work,
Here is other approach that you can have:
Using your own middleware
Another approach that is more agnostic is to use a middleware
import { Middleware } from 'redux'
import { monitorActions } from './monitorSlice';
const monitorMiddleware: Middleware = store => next => action => {
if (!monitorActions.startConnecting.match(action)) {
return next(action);
}
const connection = $.hubConnection("http://x.x.x.x/signalr", { useDefaultPath: false })
const proxy = connection.createHubProxy("exampleHub")
proxy.on("action", function(data) {
dispatch(setStatus(data))
});
next(action);
}
export default monitorMiddleware;
Then inside your react app when you want to establish the connection
useEffect(()=> {
dispatch(monitorActions.startConnecting())
}, [])
Then when you setup your store, do not forget to add your new middleware monitorMiddleware:
export const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => {
return getDefaultMiddleware().concat([monitorMiddleware])
},
});
Using create api
Another approach is to use createApi.

I want to use this. $ axios with Vuex constants

What I want to come true
I use this.$axios many times, so I tried to put it in a constant, but it doesn't work.
I read the official docs but didn't understand.
Is it because this isn't available in the Nuxt.js lifecycle?
Code
url.js
export const AXIOS_POST = this.$axios.$post
export const POST_API = '/api/v1/'
export const POST_ITEMS_API = '/api/v1/post_items/'
Vuex
import * as api from './constants/url.js' // url.js in this.
export const state = () => ({
list: [],
hidden: false
})
export const mutations = {
add (state, response) {
state.list.push({
content: response.content,
status: response.status
})
},
remove (state, todo) {
state.list.splice(state.list.indexOf(todo), 1)
},
edit (state, { todo, text }) {
state.list.splice(state.list.indexOf(todo), 1, { text })
},
toggle (state, todo) {
todo.status = !todo.status
},
cancel (state, todo) {
todo.status = false
},
// アクション登録パネルフラグ
switching (state) {
state.hidden = !state.hidden
}
}
export const actions = {
post ({ commit }, text) {
//I want to use it here
this.$axios.$post(api.POST_ITEMS_API + 'posts', {
post_items: {
content: text,
status: false
}
})
.then((response) => {
commit('add', response)
})
}
}
Error
Uncaught TypeError: Cannot read property '$axios' of undefined
Since your file is located into a constants directory, you should probably use some .env file.
Here is a guide on how to achieve this in Nuxt: https://stackoverflow.com/a/67705541/8816585
If you really want to have access to it into a non .vue file, you can import it as usual with something like this
/constants/url.js
import store from '~/store/index'
export const test = () => {
// the line below depends of your store of course
return store.modules['#me'].state.email
}
PS: getters, dispatch and everything alike is available here.
Then call it in a page or .vue component like this
<script>
import { test } from '~/constants/url'
export default {
mounted() {
console.log('call the store here', test())
},
}
</script>
As for the lifecyle question, since the url.js file is not in a .vue file but a regular JS one, it has no idea about any Vue/Nuxt lifecycles.

Use Vuex in Nuxt

I was able to fetch data and display them using Nuxt's Fetch API, but I want to utilize Vuex instead.
store/index.js:
import Axios from 'axios'
export const getters = {
isAuthenticated (state) {
return state.auth.loggedIn
},
loggedInUser (state) {
return state.auth.user
}
}
export const state = () => ({
videos: []
})
export const mutations = {
storeVideos (state, videos) {
state.videos = videos
}
}
export const actions = {
async getVideos (commit) {
const res = await Axios.get(`https://api.themoviedb.org/3/movie/popular?api_key=${process.env.API_SECRET}&page=${this.currentPage}`)
commit('storeVideos', res.data)
}
}
pages/test.vue:
<template>
<Moviecards
v-for="(movie, index) in $store.state.videos"
:key="index"
:movie="movie"
:data-index="index"
/>
</template>
<script>
...
fetch ({ store }) {
store.commit('storeVideos')
},
data () {
return {
prevpage: null,
nextpage: null,
currentPage: 1,
pageNumbers: [],
totalPages: 0,
popularmovies: []
}
},
watch: {
},
methods: {
next () {
this.currentPage += 1
}
}
}
...
The array returns empty when I check the Vue Dev Tools.
In fetch(), you're committing storeVideos without an argument, which would set store.state.videos to undefined, but I think you meant to dispatch the getVideos action:
export default {
fetch({ store }) {
// BEFORE:
store.commit('storeVideos')
// AFTER:
store.dispatch('getVideos')
}
}
Also your action is incorrectly using its argument. The first argument is the Vuex context, which you could destructure commit from:
export const actions = {
// BEFORE:
async getVideos (commit) {} // FIXME: 1st arg is context
// AFTER:
async getVideos ({ commit }) {}
}

In the nuxt project, other files are introduced separately into the store

sorry about my english, How can i in other js files use vuex.store in nuxt project
in store
export const state = () => ({
token: 'test',
name: '',
avatar: ''
}),
export const mutations = {
SET_TOKEN: (state, token) => {
state.token = token
}
},
export const getters = {
token: state => {
return state.token
}
}
in test.js
export function() => {
//how can i updata vuex token?
}
export function() => {
//how can i getter vuex token?
}
export default ({ app, store, route, redirect }) => {
some code
}
it can't work
Wanted to know if it's good practice to do that and what would be the best way to do that?
A basic implementation would look like this
import { mapState, mapGetters, mapActions } from 'vuex'
export default {
computed: {
// you only need State OR Getter here not both!!! You don't need a
// getter for just returning a simple state
...mapState('yourStoreName', ['token'])
...mapGetters('yourStoreName', ['token']),
},
methods: {
methodThatNeedsToChangeState (){
this.setToken('newToken')
},
...mapActions('yourStoreName', ['setToken']),
}
}
In your store you need actions though, you don't call mutations directly! Because Mutations can't be asynchronous.
export const actions = {
setToken: (context, token) => {
context.commit(SET_TOKEN, token)
}
},
I would highly recommend you to study the Vuex documentation in more detail.
https://vuex.vuejs.org/guide/

How to access this variable from my store / state in my vue router?

I have setup my routes.js file to import my state from my store. This is working as when I console.log(state) it outputs my store to console successfully:
I then define my route as below:
routes.js
import { state } from './store/store';
// import { mapState, mapGetters } from "vuex";
console.log(state)
const routes = [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/dashboard',
name: 'dashboard',
component: Dashboard,
},
{
path: '/project/:id',
name: 'project',
component: Project,
props: true,
meta: {
requiresAuth: true,
},
children: [
{
path: 'module/:module',
name: 'module',
component: Tasks,
props: true,
children: [
{
path: 'task/:url',
name: 'task',
component: () => import(`./components/ProductServiceAnalysis/${$state.taskURL}.vue`),
props: true,
I am getting the error:
app.js:59653 [vue-router] Failed to resolve async component default: ReferenceError: state is not defined in relation to the second last line where I try to access the state.taskURL variable.
Why is this erroring? And how can I access my taskURL variable in my store from my Router?
If I am approaching this incorrectly then please offer suggestions.
This is my store.js:
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios'
Vue.use(Vuex);
axios.defaults.baseURL = 'http://buildmybusiness.test/api'
Vue.config.devtools = true;
export const state = {
token: localStorage.getItem('access_token') || null,
requiredTask: 'This is my current task',
currentModule: '1',
currentModuleName: 'Product & Service Analysis',
currentTask: '1',
modules:[],
tasks:[],
taskName:[],
actions:[],
userId: localStorage.getItem('loggedin_user') || null,
userName: localStorage.getItem('loggedin_username') || null,
projects:[],
currentProjectId: '',
currentProjectName: '',
taskURL: 'define-product-service'
}
export const store = new Vuex.Store({
state,
mutations: {
SET_MODULES: (state, payload) => {
state.modules = payload;
},
SET_TASKS: (state, tasks) => {
state.tasks = tasks;
},
SET_MODULE_TITLE: (state, moduleTitle) => {
state.currentModuleName = moduleTitle
},
SET_ACTIONS: (state, payload) => {
state.actions = payload;
},
RETRIEVE_TOKEN: (state, token) => {
state.token = token;
},
DESTROY_TOKEN: (state) => {
state.token = null;
},
SET_USERID: (state, userid) => {
state.userId = userid;
},
DESTROY_USERID: (state) => {
state.userId = null;
},
SET_USERNAME: (state, username) => {
state.userName = username;
},
DESTROY_USERNAME: (state) => {
state.userName = '';
},
SET_PROJECTS: (state, projects) => {
state.projects = projects;
},
DESTROY_PROJECTS: (state) => {
state.projects = [];
},
SET_PROJECT_ID: (state, projectId) => {
state.currentProjectId = projectId;
},
SET_PROJECT_NAME: (state, projectName) => {
state.currentProjectName = projectName;
},
SET_ACTION_URL: (state, taskURL) => {
state.taskURL = taskURL;
},
},
getters: {
loggedIn(state){
return state.token !== null;
},
SelectedTaskURL(state) {
return state.taskURL;
}
},
actions: {
setActionsURL(context, taskURL){
context.commit("SET_ACTION_URL", taskURL);
},
setProject(context, projectDetails){
const projectId = projectDetails.projectId;
const projectName = projectDetails.projectName;
context.commit("SET_PROJECT_ID", projectId);
context.commit("SET_PROJECT_NAME", projectName);
},
fetchProjects(context) {
axios.defaults.headers.common['Authorization'] = 'Bearer ' + context.state.token;
return axios.get('/project').then(response => {
const projectNames = response.data.map(project => project);
context.commit("SET_PROJECTS", projectNames);
});
},
getUserDetails(context) {
axios.defaults.headers.common['Authorization'] = 'Bearer ' + context.state.token;
return axios.get('/user').then(response => {
const userid = response.data.id
localStorage.setItem('loggedin_user', userid)
context.commit("SET_USERID", userid);
const username = response.data.name
localStorage.setItem('loggedin_username', username)
context.commit("SET_USERNAME", username);
});
},
register(context, data) {
return new Promise ((resolve, reject) => {
axios.post('/register', {
name: data.name,
email: data.email,
password: data.password,
})
.then(response => {
resolve(response)
})
.catch(error => {
reject(error);
})
})
},
destroyToken(context){
axios.defaults.headers.common['Authorization'] = 'Bearer ' + context.state.token
if (context.getters.loggedIn){
return new Promise ((resolve, reject) => {
axios.post('/logout')
.then(response => {
localStorage.removeItem('access_token')
context.commit("DESTROY_TOKEN")
context.commit("DESTROY_USERID")
context.commit("DESTROY_USERNAME")
context.commit("DESTROY_PROJECTS")
resolve(response)
})
.catch(error => {
localStorage.removeItem('access_token')
context.commit("DESTROY_TOKEN")
context.commit("DESTROY_USERID")
context.commit("DESTROY_USERNAME")
context.commit("DESTROY_PROJECTS")
reject(error);
})
})
}
},
loadModules(context) {
axios.defaults.headers.common['Authorization'] = 'Bearer ' + context.state.token
return axios.get('/modules').then(response => {
context.commit("SET_MODULES", response.data);
});
},
getTasks(context, moduleDetails){
var moduleTitle = moduleDetails.moduleName;
var moduleTitle = (moduleTitle === undefined) ? moduleTitle = 'Product & Service Analysis' : moduleTitle;
//console.log(moduleTitle);
var moduleId = moduleDetails.moduleId;
var moduleId = (moduleId === undefined) ? moduleId = 1 : moduleId;
return axios.get(`project/${context.state.currentProjectId}/module/${moduleId}`)
.then(response => {
context.commit("SET_TASKS", response.data);
context.commit("SET_MODULE_TITLE", moduleTitle);
});
},
loadTasks(context, tasks){
},
loadActions(context){
},
retrieveToken(context, credentials){
return new Promise ((resolve, reject) => {
axios.post('/login', {
username: credentials.username,
password: credentials.password,
})
.then(response => {
const token = response.data.access_token
localStorage.setItem('access_token', token)
context.commit("RETRIEVE_TOKEN", token)
resolve(response)
})
.catch(error => {
console.log(error);
reject(error);
})
})
},
}
});
my app.js
// main.js
require('./bootstrap');
import Vue from 'vue';
import App from './App.vue';
import VueRouter from 'vue-router';
import VueAxios from 'vue-axios';
import axios from 'axios';
import routes from './routes';
import BootstrapVue from 'bootstrap-vue'
import { store } from './store/store';
import Vuex from 'vuex'
Vue.config.productionTip = false;
Vue.use(VueRouter);
Vue.use(VueAxios, axios);
Vue.use(BootstrapVue);
Vue.use(Vuex);
const router = new VueRouter({
store,
routes,
mode: 'history'
})
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!store.getters.loggedIn) {
next({
name: 'login',
})
} else {
next()
}
} else if (to.matched.some(record => record.meta.requiresVisitor)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (store.getters.loggedIn) {
next({
name: 'dashboard',
})
} else {
next()
}
} else {
next() // make sure to always call next()!
}
})
new Vue({
store: store,
router,
render: h => h(App)
}).$mount('#app');
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap-vue/dist/bootstrap-vue.css';
If reference to #shazyriver:
I have done as you suggested. I have put a console.log(./components/ProductServiceAnalysis/${state.taskURL}.vue); before const routes = [ ... which correctly accesses the taskURL property and prints it to console. However, it still fails with a 'state undefined' when it tries to access the same property from within the route itself - even though it works when accessed outside the const routes = [:
See console log for detail
First of all, your configuration and all your imports are correct.
It's very interesting issue...I have researched it and can conclude that there is a kind of bug in transpilation process of webpack+babel. Let me explain:
If you check your bundle you can see that there is strange untranspiled concat expression in your dynamic import line, something like this: ("./".concat(state.taskURL,".vue")) - but state should be wrapped with webpack helpers but it wasn't...Looks like module resolving was skipped for import statement string interpolation.
The simplest solution is just assign imported module to some variable and use that variable in the import statement(I recommend to use fully configured store instead of state):
import { store } from './store/store';
let storeVar = store;
//...
//...below
component: () => import(`./components/ProductServiceAnalysis/${storeVar.state.taskURL}.vue`),
In this case module will be processed correctly by webpack.
P.S. I had created clean project with just webpack and tried to play with dynamic imports and they was resolved successfully...So I suppose that issue in another transpilation layer, maybe babel.
P.P.S. If my explanation is not enough clear please feel free to ask in comments.
You just need to import it like:
import store from './store/store.js'
Then you can use it like:
store.commit('increaseCounter')

Categories