How to correctly mix and isolate objects in Vuex - vue.js

I'm trying to create kind of mixin for Vuex, using modules, but actions are being mixed within modules:
This is the subEvents module:
import Form from '../../classes/Form'
import * as mutationsMixin from './mixins/mutations.js'
import * as actionsMixin from './mixins/actions.js'
import * as statesMixin from './mixins/states.js'
const state = merge_objects(statesMixin.common, {
data: {},
event: null,
form: new Form({
name: null,
}),
})
const actions = merge_objects(actionsMixin, {
select() {
dd('subevent select')
},
})
const mutations = merge_objects(mutationsMixin, {
mutateSetEvent(state, payload) {
state.event = payload
},
})
dd('subEvents')
export default {
state,
actions,
mutations,
}
This is the store
/**
* Imports
*/
import Vue from 'vue'
import Vuex from 'vuex'
/**
* Vuex
*/
Vue.use(Vuex)
/**
* Global state
*/
import * as actions from './actions'
import * as getters from './getters'
import * as mutations from './mutations'
/**
* Modules
*/
import gate from './modules/gate'
import events from './modules/events'
import subEvents from './modules/subEvents'
import categories from './modules/categories'
import people from './modules/people'
import roles from './modules/roles'
import institutions from './modules/institutions'
import environment from './modules/environment'
/**
* State
*/
const state = {
mounted: false,
}
/**
* Store
*/
let store = new Vuex.Store({
state,
actions,
getters,
mutations,
modules: {
events,
people,
categories,
environment,
subEvents,
gate,
roles,
institutions,
},
})
store.dispatch('environment/absorbLaravel')
export default store
This is the merge_object helper:
window.merge_objects = (target, ...sources) => {
return Object.assign(target, ...sources)
}
So if you look at the store imports, you'll see that subEvents are being loaded after events, and the action select() (originally coming from the mixin) in the subEvents store above is being overloaded, but when I call events/select(), which is not overloaded, I get 'subevent select' message in console (dd() is a helper for that)
This is an image explaining it a little

The problem is that Object.assign does not create a copy, but instead modifies the object you pass it as the first argument. The function will return the first object.
const a = {};
Object.assign(a, { a: 1 });
console.log(a); // { a: 1 }
In your case I don't think you need a deep clone of your object. You just want to not modify the mixin directly. If you call Object.assign with a newly created object as the first argument, you will do a shallow copy of all the objects that are in the next arguments.
Object.assign({}, target, ...sources)
const a = {};
Object.assign({}, a, { a: 1 });
console.log(a); // {}
If you need a deep clone (e.g. because there are nested objects in your object that you do not want to share between instances, I would recommend using something like lodash.merge.

Related

Cannot access state of vuex module

This is my first project with Vue and I am having some trouble accessing state of vuex module.
I am currently using quasar.
Here is my code calling the state.
mounted() {
console.log("Connected", this.$store.hasModule("module"));
console.log("Module: ", this.$store.state.module);
console.log("State: ", this.$store.state.module.count)
console.log("Map State: ", this.count);
},
computed: {
// look up in `some/nested/module`
...mapState({
count: state => state.module.count // Uses the state of the module
})
},
And here is the output I am getting
Console output
Connected true
OverviewPage.vue?1b50:26 Module: Object
OverviewPage.vue?1b50:27 State: undefined
OverviewPage.vue?1b50:28 Map State: undefined
Here are my vuex codes.
This is index of the base vuex(where vuex is put).
import Vue from "vue";
import Vuex from "vuex";
// import example from './module-example'
Vue.use(Vuex);
/*
* If not building with SSR mode, you can
* directly export the Store instantiation;
*
* The function below can be async too; either use
* async/await or return a Promise which resolves
* with the Store instance.
*/
export default function(/* { ssrContext } */) {
const Store = new Vuex.Store({
modules: {
module: () => import("src/store/module-example/index.js")
},
// enable strict mode (adds overhead!)
// for dev mode only
strict: process.env.DEBUGGING
});
return Store;
}
This is the index inside my 'module' folder
import state from "./state";
import * as getters from "./getters";
import * as mutations from "./mutations";
import * as actions from "./actions";
export default {
namespaced: true,
getters,
mutations,
actions,
state
};
And this is my state file inside that module.
export default function() {
return {
count: 0
};
}
I have never used vuex before so, I am not really sure where I am getting this wrong.
If you have any solutions or advices for this, I would very much appreciate it! :)
Thanks ahead!

Why vuex return object instead of array in Quasar App?

I have Quasar App
I connected vuex
I created a separate file as module for vuex like this:
I created an empty array in vuex
If I open Vue extension in browser I see that vuex return object where an array is stored instead of just an array
How can I store just an array instead of object?
I created a vuex file like this:
in store/index.js file I imported cars.js file:
import Vue from 'vue'
import Vuex from 'vuex'
import cars from "src/store/module-example/cars";
Vue.use(Vuex)
export default function (/* { ssrContext } */) {
const Store = new Vuex.Store({
modules: {
cars
},
// enable strict mode (adds overhead!)
// for dev mode only
strict: process.env.DEBUGGING
})
return Store
}
Cars.js file:
import axios from "axios";
const state = {
cars: []
}
const mutations = {
SET_CARS: (state, cars) => {
state.cars = cars;
}
}
const actions = {
async GET_ALL_CARS_FROM_API({commit}) {
let result = await axios.get('http://localhost:8050/api/cars');
commit("SET_CARS", result.data)
}
}
const getters = {
GET_ALL_CARS: (state) => {
return state.cars;
}
}
export default {
namespaced: true,
state,
mutations,
actions,
getters
}
result.data is an object. In order to set cars to a plain array just set it to result.data.cars
commit("SET_CARS", result.data.cars)

Separating store into separate files (actions, mutations, getters) get api calls now not working

I'm trying to spilt up my store firstly so all the getters, actions and mutations are in separate files as per this answer: https://stackoverflow.com/a/50306081/5434053
I have API calls in a services file, the POST api calls seem to work but the GET ones do not, the action seems to get nothing back. Have I missed something?
import Vue from 'vue';
import Vuex from 'vuex';
import actions from './actions';
import getters from './getters';
import mutations from './mutations';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
status: '',
token: localStorage.getItem('token') || '',
user: {},
movie: {},
movies: {}
},
actions,
getters,
mutations,
});
store/actions.js
import axios from 'axios'
import {APIService} from '../services/APIService';
const apiService = new APIService();
let getMovies = async ({commit, state, getter}) => {
try {
await apiService.getMovies(localStorage.getItem('token')).then((data, error) => {
for (const movie of data.data.data.movies) {
movie.edit = false;
movie.deleted = false;
}
this.movies = data.data.data.movies;
console.log(data)
commit("fetch_movies", this.movies);
})
} catch(error) {
commit('auth_error')
localStorage.removeItem('token')
console.log(error)
}
}
export default {
getMovies,
};
It looks like you are importing actions from ./actions. However, when I look at your file at store/actions.js you are not exporting anything.
For JavaScript modules to work you have to add export statements to your files - so you can import the exported variables/properties somewhere else.
Also: You seem to only declare the function getMovies() without adding it to an actions object (which you import in store.js).
Try this:
// in store/actions.js
// your code..
const actions = {
getMovies,
}
export default actions;
Edit:
Just noticed you also use this in your action. If I am not mistaken it should be undefined as you only work with lambdas (Arrow Functions).

Correct setup and use of VUEX store mutation-types

I'm developing a Chrome extension using one of the Vue js boilerplates from Github. The default boilerplate setup is as follows:
store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import mutations from './mutations';
import * as actions from './actions'; // all actions are imported as separate vars
Vue.use(Vuex);
export default new Vuex.Store({
state: { },
mutations,
actions
});
Then in actions.js
import * as types from './mutation-types';
export const setFoo = ({ commit }, payload) => {
commit(types.SET_FOO, payload); // SET_FOO is defined in the mutation-types file
};
I think the above approach lacks a fundamental reason why we want to use mutation types file - to avoid retyping the names for mutations and actions.
So instead, I came up with a different approach:
store/index.js
...
import actions from './actions'; // actions are imported as separate functions
...
Then in actions.js
import * as types from './mutation-types';
export default {
[types.UPDATE_FOO] ({commit}, payload) {
commit(types.UPDATE_FOO, payload);
}
}
Then anywhere in the extension, we could also import mutation-types and dispatch actions using const names like so:
store.dispatch(types.UPDATE_FOO, 'some value');
The second approach seems to be more practical in terms of naming and then dispatching/committing our actions/mutations. Or could there be any issues with the latest?
Which of the above, would be generally better practice?
The first approach is preferable, but it's completely up to you. Similar approach is used in official Vuex docs.
Refrence
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// we can use the ES2015 computed property name feature
// to use a constant as the function name
[SOME_MUTATION] (state) {
// mutate state
}
}
})
// actions.js
actions: {
checkout ({ commit, state }, products) {
// save the items currently in the cart
const savedCartItems = [...state.cart.added]
// send out checkout request, and optimistically
// clear the cart
commit(types.CHECKOUT_REQUEST)
// the shop API accepts a success callback and a failure callback
shop.buyProducts(
products,
// handle success
() => commit(types.CHECKOUT_SUCCESS),
// handle failure
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}

How can I use Vuex modules state without nested objects?

I'm trying to convert existing Vuex state to modules. I'm trying to use modules like this:
const moduleA = {
state: {},
mutations: {
update (state, payload) { // payload is an object
state = payload // doesn't work
}
},
I'm registering modules:
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
The question is: is it possible to use state without nesting as I'm trying to do? Or the only way is to have something like:
const moduleA = {
state: {
one: {}
},
mutations: {
update (state, payload) {
state.one = payload // it works
},
},
}
Ideally, I'd like to have the same structure as it was before and being able to get the state as state.a instead of state.a.one
Thank you
Yes you can -- just refactor the modules into separate files and then use an index file to bring them all together:
import Vue from 'vue'
import Vuex from 'vuex'
import users from './store.users.js'
import entries from './store.entries.js'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
users,
entries
}
})
export default store
Then in each module's file (e.g. store.entries.js, you can follow the pattern you're familiar with, but with namespaced set to true:
const module = {
namespaced: true,
state: {},
getters: {},
mutations: {},
actions: {}
}
module.actions.update = ({ commit, state }, payload) => {
// commit, state, etc refer to the local store
}
export default module
For files using the store, you can import the index and then call by module:
import store from '../stores/store.index.js'
// ...
store.dispatch('entries/update', payload)
You can use state = Object.assign(state, payload). But note that this means you keep all existing props and shallow merge new ones in.
You cannot create a new state object unless you lift the logic up to the global namespace and use dynamic module registration