Closed kpturner closed 5 years ago
championswimmer.in/vuex-module-decorators
On Wed 2 Jan, 2019, 11:30 PM Kevin Turner <notifications@github.com wrote:
Having been frustrated by vuex-typescript I thought I would give this ago but I find that I am struggling without, apparently, any examples of how to import and use the modules (and the getters, actions etc) in a standard Typescript Vue component.
It looks like it could be a useful library but the README is too lightweight at the moment. Any chance of some better, more complete examples?
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/championswimmer/vuex-module-decorators/issues/80, or mute the thread https://github.com/notifications/unsubscribe-auth/ABQ_ynq5IVdSW9nG_Jwdq7Z7yVRLdkrgks5u_POkgaJpZM4ZnATN .
Thanks for the quick response. Maybe the README on the npm package should point to that documentation? Or does it?
Anyway I am still getting troubles. I have a basic modules like this:
import { getModule, Module, VuexModule, Mutation, Action } from 'vuex-module-decorators';
@Module({ stateFactory: true })
class CounterModule extends VuexModule {
private count: number = 0;
@Mutation
public increment(delta: number): void {
this.count += delta;
}
@Mutation
public decrement(delta: number): void {
this.count -= delta;
}
// action 'incr' commits mutation 'increment' when done with return value as payload
@Action({ commit: 'increment' })
public incr(): number {
return 5;
}
// action 'decr' commits mutation 'decrement' when done with return value as payload
@Action({ commit: 'decrement' })
public decr(): number {
return 5;
}
get getCount(): number {
return this.count;
}
}
export default getModule(CounterModule);
as per your example. I am registering it to the store as directed and that seems OK.
I import it in a Vue component like this:
import counterModule from '../store/modules/test_module';
(possibly this is wrong?)
I set a piece of data like this:
data() {
return {
counter: counterModule.getCount
};
}
My app crashes with
{ Error: ERR_GET_MODULE_NO_STATICS : Could not get module accessor.
Make sure your module has name, we can't make accessors for unnamed modules
i.e. @Module({ 'something' })
at getModule (/Users/turnerk/Documents/GitHub/private/squareadmin/node_modules/vuex-module-decorators/dist/cjs/index.js:22:15)
at Object.<anonymous> (src/app/store/modules/test_module.ts:39:0)
at __webpack_require__ (webpack:/webpack/bootstrap ed6ad46a43b35ba4ffec:25:0)
at Object.<anonymous> (src/app/store/index.ts:1:0)
at __webpack_require__ (webpack:/webpack/bootstrap ed6ad46a43b35ba4ffec:25:0)
at webpackContext (server-bundle.js:4362:9)
at getModule (.nuxt/store.js:57:0)
at Object.module.exports.map../index.ts (.nuxt/store.js:22:0)
at __webpack_require__ (webpack:/webpack/bootstrap ed6ad46a43b35ba4ffec:25:0)
at Object.<anonymous> (.nuxt/index.js:1:0)
at __webpack_require__ (webpack:/webpack/bootstrap ed6ad46a43b35ba4ffec:25:0)
at Object.<anonymous> (.nuxt/server.js:1:0)
at __webpack_require__ (webpack:/webpack/bootstrap ed6ad46a43b35ba4ffec:25:0)
at server-bundle.js:92:18
at Object.<anonymous> (server-bundle.js:95:10)
at evaluateModule (/Users/turnerk/Documents/GitHub/private/squareadmin/node_modules/vue-server-renderer/build.js:8368:21) statusCode: 500, name: 'NuxtServerError' }
After much faffing around I defined the module like this:
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators';
@Module({ stateFactory: true, name: 'counter' })
export default class CounterModule extends VuexModule {
private count: number = 0;
@Mutation
public increment(delta: number): void {
this.count += delta;
}
@Mutation
public decrement(delta: number): void {
this.count -= delta;
}
// action 'incr' commits mutation 'increment' when done with return value as payload
@Action({ commit: 'increment' })
public incr(): number {
return 5;
}
// action 'decr' commits mutation 'decrement' when done with return value as payload
@Action({ commit: 'decrement' })
public decr(): number {
return 5;
}
get getCount(): number {
return this.count;
}
}
Then accessed it in my Vue component like this:
import CounterModule from '../store/modules/test_module';
import { getModule } from 'vuex-module-decorators';
let counterModule: CounterModule;
Then
created() {
counterModule = getModule(CounterModule, this.$store);
}
Then I was able to access methods etc elsewhere - for example
computed: {
counter() {
return counterModule.getCount
}
}
But it seems a bit contrived. Is this a good way or is there a better way, considering that I am not using vue-class-component
?
You might want to take a look at this maybe https://github.com/coding-blocks-archives/realworld-vue-typescript
Should give you a good place to start from.
On Thu 3 Jan, 2019, 1:43 AM Kevin Turner <notifications@github.com wrote:
After much faffing around I defined the module like this:
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators';
@Module({ stateFactory: true, name: 'counter' }) export default class CounterModule extends VuexModule { private count: number = 0;
@Mutation public increment(delta: number): void { this.count += delta; } @Mutation public decrement(delta: number): void { this.count -= delta; } // action 'incr' commits mutation 'increment' when done with return value as payload @Action({ commit: 'increment' }) public incr(): number { return 5; } // action 'decr' commits mutation 'decrement' when done with return value as payload @Action({ commit: 'decrement' }) public decr(): number { return 5; } get getCount(): number { return this.count; }
}
Then accessed it in my Vue component like this:
import CounterModule from '../store/modules/test_module'; import { getModule } from 'vuex-module-decorators'; let counterModule: CounterModule;
Then
created() { counterModule = getModule(CounterModule, this.$store); }
Then I was able to access methods etc elsewhere - for example
computed: { counter() { return counterModule.getCount } }
But it seems a bit contrived. Is this a good way or is there a better way, considering that I am not using vue-class-component ?
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/championswimmer/vuex-module-decorators/issues/80#issuecomment-450971874, or mute the thread https://github.com/notifications/unsubscribe-auth/ABQ_yvMNj_xWA4fu0yOoW89-SHHKvahKks5u_RMDgaJpZM4ZnATN .
I think that is where I got my examples from to get this far. Am I barking up the wrong tree?
Ultimately I may be stuffed because this is a nuxt application and in my old modules I can use this.$axios
to make API calls from actions, but I do not know how I can get access to nuxt objects through a module written with vuex-module-decorators
I have it all working OK now (even with nuxtjs.$axios
) but I am still having to use getModule
within the components themselves so I am able to pass the store to it.
In all your examples in https://github.com/coding-blocks-archives/realworld-vue-typescript getModule
is called/exported at the end of the vuex module definition without having to pass the store. If I try that it just crashes with an error telling me I haven't supplied the store. I don't know how I can do that properly. Any clues?
To prevent that error you have to pass the store into the decorator like this line
How would you do that with a nuxt application? You cannot import the store as shown in that example because the store definition looks like this:
import Vuex from 'vuex';
import { IState } from '../types';
import counter from '../store/modules/test_module';
import hgapi from '../store/modules/test_module.1';
export default () => new Vuex.Store<IState>({
modules: {
counter,
hgapi
}
});
It returns a function, not a ready made store - and I cannot call the function to create the store from within the decorated module. I need the store already created.
OK since it is working by me calling getModule
from components (where I have access to the store) I have just created a helper function to use there. Seems to work well so far.
@kpturner can you show your final code?
Yes - and I would be immensely grateful if somebody could/would help me improve it:
Example UserStore module
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators';
import axios from '../../common/axiosAccessor';
import { IGetByIdPayload, IUser, ISocialLoginPayload, ISocialLoginResult, IStrategy } from '../../types';
import auth from '../../common/authAccessor';
import { get } from 'lodash';
export interface IUserModuleState {
userData: Partial<IUser>;
errs: string[];
infos: string[];
}
@Module({ stateFactory: true, namespaced: true, name: 'userStore' })
export default class UserStore extends VuexModule {
public user: IUserModuleState = {
userData: null,
errs: null,
infos: null
};
@Mutation
public update(user: Partial<IUser>): void {
this.user.userData = {
...user
};
}
@Mutation
public setErrs(errors: string[]): void {
this.user.errs = errors;
}
@Mutation
public setInfos(infos: string[]): void {
this.user.infos = infos;
}
@Action({ commit: 'update' })
public async fetchUser(payload: IGetByIdPayload): Promise<Partial<IUser>> {
this.setErrs(null);
this.setInfos(null);
let data: IUser;
try {
data = await axios.$post('/api/user/get-user', payload);
} catch (err) {
this.setErrs([err]);
}
if (!data) {
data = {} as IUser;
}
return data;
}
@Action({ commit: 'update' })
public async socialLogin(payload?: ISocialLoginPayload): Promise<Partial<IUser>> {
this.setErrs(null);
this.setInfos(null);
const context: any = this.context;
const configPath: string = 'auth.strategies';
let strategies: IStrategy[] = get(context.rootState.configStore.config, configPath);
if (!strategies) {
await context.dispatch('configStore/fetchConfig', { path: configPath }, { root: true });
strategies = get(context.rootState.configStore.config, configPath);
}
const usernameField: string = strategies.find(strategy => strategy.key === context.rootState.auth.strategy).usernameField;
const outboundPayload: ISocialLoginPayload = payload ? payload : {
user: this.context.rootState.auth.user,
username: this.context.rootState.auth.user[usernameField],
provider: this.context.rootState.auth.strategy
};
let result: ISocialLoginResult;
try {
result = await axios.$post('/api/user/social-login', outboundPayload);
if (result.errs) {
this.setErrs(result.errs);
auth.logout();
}
} catch (err) {
this.setErrs([err]);
}
return result.user;
}
@Action({ commit: 'update' })
public initUser(payload: Partial<IUser>): Partial<IUser> {
this.setErrs(null);
this.setInfos(null);
const data: Partial<IUser> = payload ? payload : {} as Partial<IUser>;
return data;
}
@Action
public async saveUser(createMode: boolean): Promise<boolean> {
this.setErrs(null);
this.setInfos(null);
try {
const response: any = await axios.$post('/api/user/save-user', {
user: this.userData,
newUser: createMode
});
this.setInfos(response.infos);
this.setErrs(response.errs);
} catch (err) {
this.setErrs([err]);
}
return (!this.errs || this.errs.length === 0);
}
get userData(): Partial<IUser> {
return this.user.userData;
}
get errs(): string[] {
return this.user.errs;
}
get infos(): string[] {
return this.user.infos;
}
}
Setting up the store:
import Vuex from 'vuex';
import { IState } from '../types';
import UserStore from './modules/user_store';
export default () => new Vuex.Store<IState>({
modules: {
userStore: UserStore
.... plus others
}
});
I then have this helper
import { getModule } from 'vuex-module-decorators';
import UserStore from './modules/user_store';
import { Store } from 'vuex';
let initialised: boolean = false;
let userStore: UserStore;
function initialiseStores(store: Store<any>): void {
if (!initialised) {
userStore = getModule(UserStore, store);
initialised = true;
}
}
export { initialiseStores, userStore };
Then in components and pages:
import { initialiseStores, userStore, blah, blah } from '../store/storeModules';
...
initialiseStores(this.$store);
await userStore.fetchUser({ id: this.$auth.user.id });
But I would rather export getModule
from the module definition as shown in the docs - but I don't seem to have access to the main Vuex store at that stage. It just throws a load of errors. It is as if its a dynamic module when I haven't defined it as such.
How about a factory in your user store module:
import { getModule } from 'vuex-module-decorators';
...
export const getStore = (store?: Store<any>): UserStore => getModule(UserStore, store)
@Module({ stateFactory: true, namespaced: true, name: 'userStore' })
export default class UserStore extends VuexModule {
...
and then to use:
import UserStore, { getStore as getUserStore } from '~/modules/user_store'
...
created () {
const userStore: UserStore = getUserStore(this.$store)
}
@kpturner Did you find any sophisticated solution for exporting getModule from a module?
@Yackbz @kpturner I've added a potential solution as a pull request: #124. I'd be very grateful for your thoughts and improvements :smile:
My takeaway is that initialising modules with a vuex store plugin really improves usability of vuex-module-decorators for me in NuxtJS. I'm also happy to share the way I access axios in modules, if you think that would be useful.
I'll look into it. Thanks. Did you also try to invoke nuxtServerIniti()?
async nuxtServerInit() {
console.log("RUNNING");
}
This doesn't seem to run.
@Action({rawError: true}). This is added as a decorator.
Yes, I'd like to see the how you use axios.
@yackbz you would add nuxtServerInit as you normally do in Nuxt. ~/store/index.ts
is completely vanilla, not using vuex-module-decorators at all. So just see NuxtJS documentation for that. Example:
export const actions: ActionTree<RootState, RootState> = { async nuxtServerInit(context, server ) { }
}
I've not included that in the file to avoid overcomplicating it.
@danielroe I used it inside a module and it won't run.
@Module({ namespaced: true, name: "userStore", stateFactory: true })
export class UserStore extends VuexModule {
@Action
async nuxtServerInit() {...}
}
Can it only be used inside index.ts?
See NuxtJS documentation. ServerInit is only called in index.ts
. you have to call module actions from there if you want to initialise modules. Again, there is lots of NuxtJS documentation on this.
@danielroe Thanks for the advice. Do you might have typescript nuxt vuex-module-decorator repository you can share?
@danielroe Your pr looks promising. Intellisense works correctly but the store doesn't seem to be setup correctly.
I have these files setup.
// ~/utils/store.accessor.ts
import { Store } from 'vuex'
import { getModule } from 'vuex-module-decorators'
import {PostStore} from '~/store/post-store'
let postStore: PostStore
function initialiseStores(store: Store<any>): void {
postStore = getModule(PostStore, store)
}
export {
initialiseStores,
postStore,
}
// ~/store/index.ts
import { Store } from 'vuex'
import { initialiseStores } from '~/utils/store-accessor'
const initializer = (store: Store<any>) => initialiseStores(store)
export const plugins = [initializer]
export * from '~/utils/store-accessor'
// ~/store/post-store.ts
@Module({ name: "postStore", stateFactory: true })
export class PostStore extends VuexModule {
/* -------- STATE --------
*
*/
loadedPosts: object[] = [];
/* -------- GETTERS --------
*
*/
get getLoadedPosts() {
return {posts:this.loadedPosts, test: "YES"};
}
...
}
I'm accessing the poststore like this.
// ~/pages/index.vue
import { postStore } from "~/store";
@Component({})
export default class Index extends Vue {
get loadedPosts() {
console.log("TEST", postStore.getLoadedPosts);
return postStore.getLoadedPosts
}
}
loadedPosts is unedfined. Can you tell me what's wrong? --> I solved it. I had to use a default exports
Where do you put your nuxtServerInit() if index.ts doesn't have any functions or objects?
@Yackbz In Nuxt, index.ts can have export functions and objects, and that is where your nuxtServerInit() belongs. That function can then call server initialisation functions in modules.
So, for example:
import { ActionTree, Store } from 'vuex'
import Vue from 'vue'
import { initialiseStores, sampleStore } from '~/utils/store-accessor'
import { RootState } from '~/types/state'
const initializer = (store: Store<any>) => initialiseStores(store)
export const plugins = [initializer]
export const actions: ActionTree<RootState, RootState> = {
async nuxtServerInit(_context, server: { req: IncomingMessage; app: Vue }) {
await sampleStore.serverInit(server)
},
async nuxtClientInit(context) {
await sampleStore.clientInit()
},
}
export * from '~/utils/store-accessor'
Note that the above code doesn't use vuex-module-decorators for the index.ts, but it does for everything else. Also see #124 for an example of what the store-accessor would look like.
Having been frustrated by
vuex-typescript
I thought I would give this ago but I find that I am struggling without, apparently, any examples of how to import and use the modules (and the getters, actions etc) in a standard Typescript Vue component.It looks like it could be a useful library but the README is too lightweight at the moment. Any chance of some better, more complete examples?