paroi-tech / direct-vuex Goto Github PK
View Code? Open in Web Editor NEWUse and implement your Vuex store with TypeScript types. Compatible with the Vue 3 composition API.
License: Creative Commons Zero v1.0 Universal
Use and implement your Vuex store with TypeScript types. Compatible with the Vue 3 composition API.
License: Creative Commons Zero v1.0 Universal
It seems that defining our modules is done like this:
export default {
namespaced: true,
state: { },
mutations: { },
// etc
} as const;
Do you know if there's a way to make this of type ModuleOptions
?
I tried it like this, but that seems to mess with typescript/intellisense:
const module: ModuleOptions = {
//...
};
export default module;
(What an amazing library btw! I'm trying to migrate our current code base to this and so far I'm very happy.)
Please, help me to understand why this is failing. I started to use this awesome module, however, I'm facing some troubles with Jest.
Here is my module index.ts (mainly the same but I cannot paste code here due to NDA):
// store/modules/my-module/index.ts
...
export const getDefaultState = (): CoreState => {
return {
...
}
}
// Deep clone the default state to the store state object:
const state = (): CoreState => getDefaultState()
const myModule = {
namespaced: true as true,
state,
getters,
actions,
mutations,
}
export const myModuleActionContext = (context: any) => moduleActionContext(context, myModule)
export const myModuleGetterContext = (args: [any, any, any, any]) => moduleGetterContext(args, myModule)
export default myModule
And here is how I create my (big) store:
...
import myModule from '@store/modules/my-module'
...
import { createDirectStore } from 'direct-vuex'
...
const {
store,
rootActionContext,
moduleActionContext,
moduleGetterContext,
} = createDirectStore({
state,
actions,
getters,
mutations,
modules: {
...
myModule,
...
},
strict: true,
})
const original = store.original
// Export the classic Vuex store.
export default original
const direct = store
export {
// The following exports will be used to enable types in the
// implementation of actions.
rootActionContext,
moduleActionContext,
moduleGetterContext,
// Export the direct-store
direct,
}
// The following lines enable types in the injected store '$store'.
export type AppStore = typeof store
declare module 'vuex' {
interface Store<S> {
direct: AppStore
}
}
And then, my test file:
import { direct as store } from '@store'
describe('My module store', () => {
...
const myModuleState = store.state.myModule
const rootState = store.state
const vueState = {
state: myModuleState,
store,
rootState,
rootGetters: store.getters,
mockedActions: actions,
}
// Test mutations
test('Store mutation: X', () => {
...
})
...
The error is about this:
TypeError: Cannot read property 'getters' of undefined
It will be useful for developing with native es modules in browser.
https://github.com/paleo/direct-vuex#in-a-vuex-module
const mod1ActionContext = (context: any) => moduleActionContext(context, mod1)
<R,O>
should be in DirectActionContext
here?const mod1ActionContext = (context: any):
DirectActionContext<R, O> => moduleActionContext(context, mod1)
Following the example in the Readme I create a getter in my root store
getters: {
myGetter(...args) {
const {state, getters} = rootGetterContext(args);
}
}
I am getting the following type error
Argument of type '[RootState, any, any, any]' is not assignable to parameter of type '[any, any]'.
Types of property 'length' are incompatible.
Type '4' is not assignable to type '2'.ts(2345)
It seems to be expecting
(method) myGetter(state: RootState, getters: any, rootState: any, rootGetters: any): void
Can I use store from config:
const storeConf = {
state: {},
actions: {},
getters: {},
mutations: {},
modules: modulesConf,
} as const;
Vue.use(Vuex);
export const { store, moduleActionContext } = createDirectStore(storeConf);
or I should use getters: createGetters<{}>(),
createActions, etc?
Hey,
Just read your Medium article and I'm looking forward to using this package! Unfortunately I'm running into an issue creating the direct store.
Below is the main index.ts for my store: store/index.ts
import Vue from 'vue';
import Vuex, { StoreOptions } from 'vuex';
import { createDirectStore } from 'direct-vuex';
import { RootState } from './types';
import profile from './profile';
Vue.use(Vuex);
const { store } = createDirectStore({
state: {},
modules: {
profile,
},
});
export default new Vuex.Store<RootState>(store);
And here is a module that I am trying to include in the store: store/profile/index.ts
import { Module } from 'vuex';
import getters from './getters';
import actions from './actions';
import mutations from './mutations';
import { ProfileState } from './types';
import { RootState } from '../types';
const state: ProfileState = {
user: undefined,
authErrorMessage: '',
authLoading: false,
};
const profile: Module<ProfileState, RootState> = {
namespaced: true as true,
state: {
user: undefined,
authErrorMessage: '',
authLoading: false,
},
getters,
actions,
mutations,
};
export default profile;
The modules
property in store/index.ts
(rather the profile
property) is causing a type assignability error.
Type '{ profile: Module<ProfileState, RootState>; }' is not assignable to type '{ profile: Module<ProfileState, RootState>; } & ModulesImpl<any>'.
Type '{ profile: Module<ProfileState, RootState>; }' is not assignable to type 'ModulesImpl<any>'.
Property 'profile' is incompatible with index signature.
Type 'Module<ProfileState, RootState>' is not assignable to type 'ModuleOptions<any>'.
Types of property 'actions' are incompatible.
Type 'ActionTree<ProfileState, RootState> | undefined' is not assignable to type 'ActionsImpl | undefined'.
Type 'ActionTree<ProfileState, RootState>' is not assignable to type 'ActionsImpl'.
Index signatures are incompatible.
Type 'Action<ProfileState, RootState>' is not assignable to type 'ActionImpl'.
Type 'ActionObject<ProfileState, RootState>' is not assignable to type 'ActionImpl'.
Type 'ActionObject<ProfileState, RootState>' provides no match for the signature '(context: ActionContext<any, any>, payload: any): any'.ts(2322)
index.ts(11, 3): The expected type comes from property 'modules' which is declared here on type '{ state: {}; modules: { profile: Module<ProfileState, RootState>; }; } & StoreOptions<{}>'
Any help would be great! I am happy to provide more details.
Thanks,
I'm using vuex-module-decorators (quick intro here) to write my Vuex modules. This allows me to write class-style modules that are really nice to read and write. However, it is a bit more annoying to get a typed state though.
Is there a way to use direct-vuex together with vuex-module-decorators? I'm currently getting problems when I try to create the direct store with my VuexModule from vuex-module-decorators:
// mymodule.ts
@Module({
name: "MyModule",
namespaced: true as true
})
export default class MyModule extends VuexModule {
// ...
}
// store.ts
const { store, rootActionContext, moduleActionContext } = createDirectStore({
modules: {
MyModule
}
});
Hello,
I have been using this amazing package for awhile. But I am struggling with IE 11 support. After compilation arrow functions => are left in code.
I managed to fix this by using ems version of files instead of umd. If i swap "module" and "main" in your package.json the compilation works perfectly and it runs on IE without any problems.
I don't really understand how things like EMS and UMD work, but swapping those values fixed my issue, but I don't think its the correct way to fix it :D
Thanks for any suggestions and for your work on this amazing package, HamAndRock.
This is my action:
async foo(context: any): Promise<void> {
const { rootDispatch } = typedContext(context)
rootDispatch.uiModule.notification("test")
}
And this is my test:
test.only("foo", async () => {
const dispatch = jest.fn()
await actions.foo({ dispatch })
expect(dispatch).toHaveBeenCalledWith("uiModule/notification", "test")
})
Expected: test to pass
Actual: Number of calls: 0
What am I doing wrong here?
Thanks!
Currently to access a module action you can do: this.$store.direct.dispatch.users.someAction()
Although I am wondering if it would make more sense (and be possible) to swap the order of the command (dispatch) and the module (users), like so: this.$store.direct.users.dispatch.someAction()
The benefit of the latter is that it makes it possible to return the module as a calculated property in your component to reduce bloat:
export default class UserManagement extends Vue {
private get users() {
return this.$store.direct.users;
}
public someAction() {
this.users.dispatch.someAction();
}
}
Or even better in the template:
<h2 class="title">{{users.getters.name}}</h2>
Just an fyi I'm now doing this to achieve the same thing:
private get users() {
return {
state: this.$store.direct.state.users,
getters: this.$store.direct.getters.users,
commit: this.$store.direct.commit.users,
dispatch: this.$store.direct.dispatch.users,
}
}
I'm using Rxjs Observables in my actions. When using defineActions
, action functions return Promises. How can I return Observables?
When I return an Rxjs Observable, the return type is Promise<Observable<any>>
How can I return an Observable?
For some reason I do not have access to namespaced modules 😕
// actions.ts
import { State } from './types';
import { actions } from './actions';
import { createModule } from 'direct-vuex';
import { getters } from './getters';
import { moduleActionContext } from './.store';
import { mutations } from './mutations';
const state: State = {
bar: ""
}
const fooModuleConfig = {
namespaced: true,
state,
getters,
actions,
mutations
}
export const fooModule = createModule(fooModuleConfig)
export const getTypedContext = (context: any) =>
moduleActionContext(context, fooModule)
// module.ts
import { State } from './types';
import { actions } from './actions';
import { createModule } from 'direct-vuex';
import { getters } from './getters';
import { moduleActionContext } from './.store';
import { mutations } from './mutations';
const state: State = {
bar: ""
}
const fooModuleConfig = {
namespaced: true,
state,
getters,
actions,
mutations
}
export const fooModule = createModule(fooModuleConfig)
export const getTypedContext = (context: any) =>
moduleActionContext(context, fooModule)
// store.ts
import Vue from 'vue';
import Vuex from 'vuex';
import { createDirectStore } from 'direct-vuex';
import { fooModule } from './module';
Vue.use(Vuex)
const fooStoreConfig = {
state: {},
modules: {
fooModule
},
getters: {},
actions: {},
mutations: {}
}
export const {
moduleActionContext,
store,
rootActionContext
} = createDirectStore(fooStoreConfig)
// here I try to access module
store.dispatch.fooModule.foo() // intellisense complains
store.dispatch.foo() // no complaints
Expected: That intellisense would let me access store.dispatch.fooModule.foo()
Actual: It will only let me access store.dispatch.foo()
What am I doing wrong this time? 😄
I'm getting the error:
Uncaught Error: [vuex] must call Vue.use(Vuex) before creating a store instance.
Here's my thoughts:
Perhaps direct-vuex has its own VueX installation?
Here's what fixes my problem:
myproject/src/main.ts
import Vue from 'vue'
const { store, rootActionContext, moduleActionContext } = createDirectStore(
(vuex: any) => Vue.use(vuex),
{
...
}
)
direct-vuex.esm.js
import Vuex from 'vuex'
function createDirectStore(callback, options) {
callback(Vuex)
Here's my code:
myproject/src/main.ts
import Vue from 'vue'
import { createDirectStore } from 'direct-vuex'
import vuex from 'vuex'
import tabs from '@/store/modules/tabs'
import favourites from '@/store/modules/favourites'
import VuexPersistence from 'vuex-persist'
Vue.use(vuex)
const vuexLocal = new VuexPersistence<any>({
storage: window.localStorage,
reducer: state => ({ favourites: state.favourites }),
})
const { store, rootActionContext, moduleActionContext } = createDirectStore({
modules: {
tabs,
favourites,
},
plugins: [vuexLocal.plugin],
} as any)
I am trying to implement direct-vuex in a 'boilerplate' I frequently use when setting up new projects. It is written in typescript with vuex-typex, but direct-vuex is a lot cleaner and closer to the vuex docs, so I would like to switch to it.
However, the docs here are unclear in a couple of areas, specifically with regards to actions. These are the last hurdles I have to clear in the transition.
The first issue:
Actions can't be declared with the object alternative syntax.
I am not sure what this means - I don't fully know what "alternative" means here. Could you give an example of bad/good syntax?
Also, I cannot figure out how to properly use context in vuex (actions, etc.) A sample for writing/using a vuex module would be helpful. For example, from the vuex docs:
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
Note that the first argument of the action is the context which in this example contains the module state, module commit, and root state. Likewise, in the root store, the context would have similar properties.
It seems this context argument is still required, but I can't seem to get it to work. Can you please provide an example of how to get type hinting/autocompletion on the context-specific state/commit/etc. via direct-vuex?
If direct-vuex can't get that type information, the argument is still required in actions, but is no longer useful, so it should be communicated that you can use store.state and store.commit instead.
So, more things around this library (which looks powerful!):
async someAction (context, { someParameter, someOtherParam = true }: { someParameter: any, someOtherParam?: boolean }) {
const { commit, rootDispatch } = myModuleActionContext(context)
commit.fff()
...
the line commit.fff()
should not work, at compile time, at lint time or any other place but fff
is not a valid mutation.
On the other hand, commit.
properly autocompletes available mutations and hovering over one of them properly shows the types. So commit.validMutation()
properly shows me the types.
What I need is to catch when I'm using commit.fff()
because fff
doesn't exist.
This is also happening with other use cases, but this clearly shows the problem.
Regards dude!
I read your article, and don't understand how to migrate properly.
I am using vue-property-decorator & register component store as module on beforeCreate hook:
beforeCreate(): void {
// only if not initialized yet
const store = this.$store;
if (!(store && store.state && store.state[storeName])) {
store.registerModule(storeName, storeConf);
}
}
where storeConf looks like this:
import { IState } from './state.i';
const defaultState: IState = {
iamSelect: {
value: null,
data: {
options: [],
},
},
};
...
export const storeConf = {
namespaced: true as true, // direct-vuex needs
actions,
getters,
mutations,
state: defaultState,
} as const;
I register Store (without modules) only in app file (not migrated totally yet):
const store = createDirectStore(storeConf).store.original;
Does direct-vuex support dynamic registration of modules?
Hi,
i have problem with running the app in IE 11. The problem i in function createDirectStore(options), where it exptect original: original
, insead of simple original
in const store { ... }
function createDirectStore(options) {
const original = new vuex__WEBPACK_IMPORTED_MODULE_0__["default"].Store(options);
const store = {
get state() {
return original.state;
},
getters: toDirectGetters(options, original.getters),
commit: toDirectCommit(options, original.commit),
dispatch: toDirectDispatch(options, original.dispatch),
original
};
original.direct = store;
return {
store,
rootGetterContext: ([state, getters]) => getModuleGetterContext([state, getters, state, getters], options, options),
moduleGetterContext: (args, moduleOptions) => getModuleGetterContext(args, moduleOptions, options),
rootActionContext: (originalContext) => getModuleActionContext(originalContext, options, options),
moduleActionContext: (originalContext, moduleOptions) => getModuleActionContext(originalContext, moduleOptions, options),
};
}
Is there a way to fix this? Thanks
Type error when using createDirectStore
with VuexPersistance
plugin:
The type 'readonly [Plugin]' is 'readonly' and cannot be assigned to the mutable type 'PluginImpl[]
Is there a reason PluginImp[] is mutable?
const vuexPersistance = new VuexPersistence<RootState>({
storage: localStorage,
});
export const {
store,
rootActionContext,
moduleActionContext,
} = createDirectStore({
modules: {
// filter,
},
strict: process.env.DEV ? true : false,
plugins: [vuexPersistance.plugin],
} as const);
// store/demoModule/index.ts
import actions from "./actions";
import mutations from "./mutations";
import getters from "./getters";
import { createModule } from "direct-vuex";
import { moduleActionContext } from "@/store";
export interface Todo {
title: string;
id: number;
}
export interface demoModuleState {
list: Todo[];
}
const demoModule = createModule({
state: (): demoModuleState => {
return {
list: [
{
id: 1,
title: "Hello world"
}
]
};
},
getters: getters,
mutations: mutations,
actions: actions,
namespaced: true as true
});
export default demoModule;
export const demoModuleActionContext = (context: any) =>
moduleActionContext(context, demoModule);
// store/index.ts
import Vue from "vue";
import Vuex from "vuex";
import demoModule from "./demoModule";
import { createDirectStore } from "direct-vuex";
Vue.use(Vuex);
const { store, rootActionContext, moduleActionContext } = createDirectStore({
modules: {
demoModule: demoModule
}
});
export default store;
export { rootActionContext, moduleActionContext };
export type AppStore = typeof store;
declare module "vuex" {
interface Store<S> {
direct: AppStore;
}
}
depcruise --validate .dependency-cruiser.js src
warn no-circular: src/store/index.ts →
src/store/demoModule/index.ts →
src/store/index.ts
warn no-circular: src/store/demoModule/mutations.ts →
src/store/demoModule/index.ts →
src/store/demoModule/mutations.ts
warn no-circular: src/store/demoModule/mutations.ts →
src/store/demoModule/index.ts →
src/store/demoModule/mutations.ts
warn no-circular: src/store/demoModule/index.ts →
src/store/index.ts →
src/store/demoModule/index.ts
warn no-circular: src/store/demoModule/index.ts →
src/store/demoModule/mutations.ts →
src/store/demoModule/index.ts
warn no-circular: src/store/demoModule/index.ts →
src/store/demoModule/getters.ts →
src/store/demoModule/index.ts
warn no-circular: src/store/demoModule/index.ts →
src/store/demoModule/actions.ts →
src/store/demoModule/index.ts
warn no-circular: src/store/demoModule/getters.ts →
src/store/demoModule/index.ts →
src/store/demoModule/getters.ts
warn no-circular: src/store/demoModule/actions.ts →
src/store/demoModule/index.ts →
src/store/demoModule/actions.ts
✖ 9 dependency violations (0 errors, 9 warnings). 21 modules, 33 dependencies cruised.
Hi,
I would like to test an action similar to this:
async myAction(context: any,): Promise<void> {
const { commit, rootDispatch, getters } = typedContext(context)
if (!getters.someProperty) {
rootDispatch.uiModule.notification("My message")
}
commit.DO_SOMETHING()
}
I am struggling on how to mock the context object in my test that I need to pass to my action.
test("myAction", async () => {
const context = {
...store,
commit: jest.fn()
}
await actions.myAction(context)
expect(context.commit).toHaveBeenCalledWith("DO_SOMETHING")
})
I have tried the above. But the getters returned from typedContext is an empty object.
What is the easiest way to mock to context object? :)
I changed my code to use direct-vuex in actions, browser seems to work without errors, but when I run jest tests, test suite failed to run, cause of typing when I mount component with store
src/unique.blocks/SomeForm/SomeForm.spec.ts:30:19 - error TS2769: No overload matches this call.
Overload 1 of 3, '(component: VueClass<SomeForm>, options?: ThisTypedMountOptions<SomeForm> | undefined): Wrapper<SomeForm>', gave the following error.
Argument of type '{ store: { readonly state: { modSomeForm: { iamSelect: { value: IOption | null; data: IData; }; }; }; getters: { modSomeForm: { readonly someValue: IOption | null;
readonly someValues: IOption[]; }; }; commit: { ...; }; dispatch: { ...; }; original: { ...; }; }; localVue: VueConstructor<...>; }' is not assignable to parameter of type 'ThisTypedMountOptions<SomeForm>'.
Type '{ store: { readonly state: { modSomeForm: { iamSelect: { value: IOption | null; data: IData; }; }; }; getters: { modSomeForm: { readonly someValue: IOption | null; readonly someValues: IOption[]; }; }; commit: { ...; }; dispatch: { ...; }; original: { ...; }; }; localVue: VueConstructor<...>; }' is not assignable to type 'MountOptions<SomeForm>'.
Types of property 'store' are incompatible.
Type '{ readonly state: { modSomeForm: { iamSelect: { value: IOption | null; data: IData; }; }; }; getters: { modSomeForm: { readonly someValue: IOption | null; readonly someValues: IOption[]; }; }; commit: { ...; }; dispatch: { ...; }; original: { ...; }; }' is missing the following properties from type 'Store<any>': replaceState, subscribe, subscribeAction, watch, and 3 more.
Overload 2 of 3, '(component: ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<...>>, options?: ThisTypedMountOptions<...> | undefined): Wrapper<...>', gave the following error.
Value of type 'typeof SomeForm' has no properties in common with type 'ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<...>>'. Did you mean to call it?
Overload 3 of 3, '(component: FunctionalComponentOptions<Record<string, any>, PropsDefinition<Record<string, any>>>, options?: MountOptions<Vue> | undefined): Wrapper<...>', gave the following error.
Argument of type 'typeof SomeForm' is not assignable to parameter of type 'FunctionalComponentOptions<Record<string, any>, PropsDefinition<Record<string, any>>>'.
Property 'functional' is missing in type 'typeof SomeForm' but required in type 'FunctionalComponentOptions<Record<string, any>, PropsDefinition<Record<string, any>>>'.
30 const wrapper = mount(SomeForm, { store, localVue });
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/unique.blocks/SomeForm/SomeForm.spec.ts:30:25
30 const wrapper = mount(SomeForm, { store, localVue });
~~~~~~~~
Did you mean to use 'new' with this expression?
node_modules/vue/types/options.d.ts:132:3
132 functional: boolean;
~~~~~~~~~~
'functional' is declared here.
What am I doing wrong?
First, than you very much for making vuex finally usable with TypeScript! 👍
Reading this
Direct-vuex provides several useful helpers for implementation of the store. They are all optional. However, if you want to keep your classic implementation of a Vuex Store, then direct-vuex needs to infer the literal type of the namespaced property. You can write namespaced: true as true where there is a namespaced property. But you don't need to worry about that if you use defineModule.
it seems to me that I don't have to write namespaced: true as true
to a module definition when I'm using defineModule
helper function. But that does not seem to be true.
When namespaced
attribute is omitted everything is registered in global state as expected from the documentation https://vuex.vuejs.org/guide/modules.html#namespacing .
Is that just unfortunate wording or am I doing something wrong and using defineModule
should create namespaced module automatically?
Vuex has a very useful generic StoreOptions
type to define stores:
const module: StoreOptions<UserState> = {
state: {
name: "Harry Potter"
},
getters: {
name: (state) => state.name // Here state automatically has the correct type UserState
}
}
This is how the same store should be defined with direct-vuex
. On larger stores this adds quite some bloat:
export default {
state: {
name: "Harry Potter"
} as UserStore,
getters: {
name: (state: UserStore) => state.name
}
}
Unless I'm mistaken there is no way to use this (or any other) type to achieve the same thing, because of the const assertion. Do you know if there's another way to make store creation stronger typed?
Hi Paleo,
Thanks for this great library and the great docs!
I'm using Jest for unit testing and I'm realizing that the way direct-vuex is recommended in README.md may create issues during parallelized tests.
When setting a sample app like this:
// src/store/index.ts
const {
store,
rootActionContext,
moduleActionContext,
rootGetterContext,
moduleGetterContext
} = createDirectStore({
// … store implementation here …
})
// Export the direct-store instead of the classic Vuex store.
export default store
// The following exports will be used to enable types in the
// implementation of actions and getters.
export {
rootActionContext,
moduleActionContext,
rootGetterContext,
moduleGetterContext
}
// src/main.ts
import Vue from "vue"
import store from "./store"
new Vue({
store: store.original, // Inject the classic Vuex store.
// …
}).$mount("#app")
// tests/unit/MyComponentUsingAStore.spec.ts
import { shallowMount } from '@vue/test-utils'
import MyComponentUsingAStore from '@/components/MyComponentUsingAStore.vue'
describe('MyComponentUsingAStore.vue', () => {
it('Test 1', () => {
const wrapper = shallowMount(MyComponentUsingAStore)
expect(wrapper.find('.loading').text()).toEqual("Loading...")
})
it('Test 2', () => {
const wrapper = shallowMount(MyComponentUsingAStore)
expect(wrapper.find('.loading').text()).toEqual("Loading...")
})
})
Jest is running its tests in parallel.
So, this scenario can generates errors :
MyComponentUsingAStore
's component, the created
hook of this component calls an async action, which will mutate the store in 1ms.MyComponentUsingAStore
's component, the vuex's state which has been populated in [Test 1] is used. So, the state fails.As a workaround to this, we can :
resetState()
function before each test.--runInBand
CLI parameterHowever, it's far from an ideal solution as we lose test parallelization.
A solution would be export a createStore
function instead of the store instance directly (as vuex
documentation does). It would allows the user to create a new instance of vuex
/ direct-vuex
for each test.
However, this do not seems possible because of the circular dependency between the store and the modules.
Is there another way to avoid sharing an instance of vuex
?
Thanks again :)
Thanks for the great library.
I like how simple the idea is most importantly, do not need to change much code from the traditional vuex approach.
However, it would be awesome if we can use strongly type for the ActionContext.
For example,
actions: {
fetchSomething({state, rootState}, payload: SomePayload) {
}
}
state
and rootState
should be of type ModuleState
and RootState
, instead of any
.
We recently migrated our project from tslint to eslint with typescript support.
We now get a no-explicit-any
warning for our context parameters, e.g.:
async requeueContracts(context: any) { // @typescript-eslint/no-explicit-any: Unexpected any. Specify a different type.
const { state, getters, commit, dispatch } = actionContext(context);
await myClient.requeue();
},
Do you know if it's possible to make this context typed? I would rather not ignore this eslint warning.
Hi, Paleo!
Could you please give us an example explaining how we use state in vue template?
I am really grateful for your help.
I used useState
https://vue-hooks.netlify.com/?path=/story/usestate--docs
But it doesn't seem to work.
I love your approach of making the store typescript friendly. I am looking for a while how to do this.
If I make a store with 2 modules in, I can use the state directly, but the commits and dispatches are always empty. Is this expected for now?
Hi,
Thanks for writing this great library. I can't believe this is not built into vuex. I hope vuex + typescript will be a better match once vue3 is out.
Anyway, my question is: how can I test an action now that we use moduleActionContext inside the actions?
I get:
TypeError: Invalid value used as weak map key
export const fooActionContext = (context: any) =>
moduleActionContext(context, fooModule)
My code is:
// foo.ts
import { Context } from './types';
import { fooActionContext } from './store';
export const actions = {
foo(context: Context) {
const { commit } = fooActionContext(context)
}
} as const
// fooModule.ts
import { actions } from './foo';
export const fooModule = {
namespaced: true,
state: {},
getters: {},
actions,
mutations: {}
} as const
// foo.test.ts
import { actions } from './foo';
test("foo", async () => {
const commit = jest.fn()
await actions.foo({ commit } as any)
expect(commit).toHaveBeenCalled()
})
// store.ts
import Vue from 'vue';
import Vuex from 'vuex';
import { createDirectStore } from 'direct-vuex';
import { fooModule } from './fooModule';
Vue.use(Vuex)
export const { moduleActionContext } = createDirectStore({
state: {},
modules: {
fooModule
}
} as const)
export const fooActionContext = (context: any) =>
moduleActionContext(context, fooModule)
What am I doing wrong? And how should I test an action how that we call fooActionContext() inside the action?
Thanks in advance!
How to check this code is working?
readme.md:
export type AppStore = typeof store
declare module "vuex" {
interface Store<S> {
direct: AppStore
}
}
Is it means that I can use some code instead of (working)
import { store as vueAppStore } from '~/VueApp/store';
...
vueAppStore.dispatch.modSomeForm.getSomeValues
using this?
this.$store.dispatch.modSomeForm.getSomeValues
When I typing in VSCode this.$store.dispatch
there is nothing about getSomeValues
=(
Hello,
I think that your product is super. But I have some problems with it and I was not able to google it.
When I typing the project, everything looks fine. But when I try to build it, i got error:
Property 'alert' does not exist on type 'never'.
Instead of 'alert' you can put any of my vuex store module. In development build it is compiled with this error, but everything works fine.
const SUCCESS = 'success';
const WARNING = 'warning';
const ERROR = 'error';
const CLEAR = 'clear';
const state: AlertStore = {
type: null,
message: null
}
const mutations = {
[SUCCESS](state: AlertStore, message: string) {
state.type = AlertType.Success;
state.message = message;
},
[WARNING](state: AlertStore, message: string) {
state.type = AlertType.Warning;
state.message = message;
},
[ERROR](state: AlertStore, message: string) {
state.type = AlertType.Error;
state.message = message;
},
[CLEAR](state: AlertStore) {
state.type = null;
state.message = null;
},
};
const actions = {
success({ commit }: any, message: string) {
commit(SUCCESS, message);
},
warning({ commit }: any, message: string) {
commit(WARNING, message);
},
error({ commit }: any, message: string) {
commit(ERROR, message);
},
clear({ commit }: any) {
commit(CLEAR);
},
};
const getters = {};
export interface AlertStore {
type: AlertType | null;
message: string | null;
}
export enum AlertType {
Success = 'success',
Warning = 'warning',
Error = 'error',
}
export default {
namespaced: true as true,
state: state as AlertStore,
getters,
actions,
mutations
};
Calling store.dispatch.alert.clear()
show error, that alert does not exist on type 'never'. Pointing to 'dispatch'. The same with 'getters'.
I have an action where I want it to be called with either a payload or nothing.
actions: {
async myAction(context, payload?: PayloadType) {
// ...
}
}
store.dispatch.myAction();
^---------- Expected 1 arguments, but got 0.ts(2554)
direct-types.d.ts(97, 7): An argument for 'payload' was not provided.
I also tried async myAction(context, payload: PayloadType | undefined)
but got the same error.
For now I've worked around the issue by passing in null
but it would be nice if it were possible to make the parameter optional.
Hey. In the module sample on how to use moduleActionContext, mod1ActionContext is used before it's assigned.
actions: {
loadP1(context, payload: { id: string }) {
const { dispatch, commit, getters, state } = mod1ActionContext(context)
// Here, 'dispatch', 'commit', 'getters' and 'state' are typed.
}
},
})
export default mod1
export const mod1ActionContext = (context: any) => moduleActionContext(context, mod1)
Problem is, moduleActionContext requires the module to be created, so you can't create it before either.
My workaround has been to create the module in 2 steps, first creating the module with state & mutations then assigning actions, which is not elegant:
const Module = createModule({
state: (): ModuleState => { // whatever } ,
getters: { // whatever },
mutations: { // whatever },
` actions: {}
});
export const ModuleActionContext = (context: any) => moduleActionContext(context, Module);
Module.actions = {
whatever(context: any, payload: { foo: boolean }) {
const { commit } = ModuleActionContext(context);
commit.THE_MUTATION(payload.foo);
}
};
Am I seeing something wrong?
Hi, Paleo!
I have followed your example but when calling getters, actions, mutations, the following problem occurs:
State: OK
store.state.mod1.p1 ---> OK
Getters: missing module
store.getters.p1OrDefault -----> call OK
But, I want it to be like this:
store.getters.mod1.p1OrDefault
How I can do?
Thank you for adding all createXXX
methods, they are very useful.
So far I could not figure out only one thing: how to create getters with parameters?
Something like this: https://vuex.vuejs.org/guide/getters.html#method-style-access
Is it supported? It seems like currently all getters are mapped to properties, not methods:
I don't see a changelog or any tagged releases. It's a bit hard to keep track of what has been updated, have to go back and look at each commit since last known download. Can you add a changelog, or at the very least tag things to correspond with npm releases?
I'd like to dynamically register modules via store.original.registerModule, but types registered this way are not detected. Is this a feature that can be added, or am I doing something wrong?
v 0.9.9,
Chrome 79.0.3945.79
Firefox 72.0.2
Opera 66.0.3515.44
First page loading - error, second - ok.
When I use SystemJS with direct-vuex.umd.js version & vuex.min.js I have an error:
Uncaught (in promise) TypeError: Cannot read property 'Store' of undefined
at createDirectStore (direct-vuex.umd.js:17)
It is about
Vuex = Vuex && Vuex.hasOwnProperty('default') ? Vuex['default'] : Vuex;
Tried to use console:
Vuex // is ok - object as expected
Vuex = Vuex && Vuex.hasOwnProperty('default') ? Vuex['default'] : Vuex;
is equal Vuex
May be I have that error because of vuex.min.js is loaded after direct-vuex.umd.js?
About declaring state: is it correct to declare state like this?
interface IState {
iamSelect: {
value: IamSelectOption | null;
data: IamSelectData;
};
}
const defaultState: IState = {
iamSelect: {
value: null,
data: {
options: [],
},
},
};
Can't use in browser, cause of error:
Uncaught ReferenceError: exports is not defined
at direct-vuex.js:5
I am using SystemJs for production (with ie11 support) bundle with type="systemjs-importmap"
like this:
{
"imports": {
"direct-vuex": "https://cdn.jsdelivr.net/npm/[email protected]/dist/direct-vuex.min.js",
"vue": "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js",
"vuex": "https://cdn.jsdelivr.net/npm/[email protected]/dist/vuex.min.js",
"wretch": "https://cdn.jsdelivr.net/npm/[email protected]/dist/bundle/wretch.min.js"
}
}
and all ok except direct-vuex =(
Chrome is ok, but IE11 is not.
If I remove direct-vuex code, IE11 is ok.
If not, I have error:
Unhandled promise rejection TypeError: unsupported method "entries"
stack:
at gettersFromOptions (https://cdn.jsdelivr.net/npm/[email protected]/dist/direct-vuex.umd.js:68:30)
at toDirectGetters (https://cdn.jsdelivr.net/npm/[email protected]/dist/direct-vuex.umd.js:58:13)
at createDirectStore (https://cdn.jsdelivr.net/npm/[email protected]/dist/direct-vuex.umd.js:18:9)
at e.init (http://localhost:3000/bundle.system.js:1:19479)
...
I have this error when trying to define Getters:
'userGetters' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.ts(7022)
I can access store.getters.canUpdateUsers
from my component but the getter is just executed one time and the type of getters
is any. This is the exact type of store.getters
:
(property) getters: {
[x: string]: any;
user: {
[x: string]: any;
};
}
This is the getter part of my module:
export const userGetters = defineGetters<UserState>()({
canUpdateUsers(...args) {
// const { state } = userGetterContext(args);
const { state, getters, rootState, rootGetters } = mod1GetterContext(args)
console.log(state);
return (
state.currentUser.role == "admin" ||
state.currentUser.role == "presenter"
);
},
});
const mod1GetterContext = (args: [any, any, any, any]) => moduleGetterContext(args, userModule)
And this is the module itself:
export const userModule = defineModule({
state: {
userList: [],
currentUser: {},
} as UserState,
actions: userActions,
mutations: userMutations,
getters: userGetters,
});
I already disabled no-use-before-define
eslint rule. It seems that it's not related to this error.
For actions I am using destructuring for readable declarations in parameters:
export const getSomeValues = (
{ dispatch }: { dispatch: Function },
{ label }: ISomeValue,
): Promise <Array<ISomeValue> | void> => SomeSvc
.fetchData({ label })
.then((resp) => dispatch('setSomeValues', resp));
And I have test like this:
describe('vuex SomeForm IamSelect actions', () => {
it('getPoints after getting data raise appropriate action', async () => {
expect.assertions(1);
const dispatch = jest.fn();
await actions.getSomeValues({ dispatch }, { label: 'Рим' });
expect(dispatch).toHaveBeenCalledWith('setSomeValues', expect.anything());
});
});
with stubbing dispatch/commit functions.
I'm searching also another examples of usage direct-vuex, and found some code:
export default createActions({
fetchCustomersFromDB(context) {
const { commit } = rootActionContext(context);
commit.isLoadingCustomerList(true);
stitchApi
.getAllCustomers()
.then(customers => {
// console.log("Success:", result);
commit.customers(customers);
commit.isLoadingCustomerList(false);
})
.catch(err => {
console.error(`Failed: ${err}`);
commit.isLoadingCustomerList(false);
});
},
});
If I declare input parameters in actions through rootActionContext
or (for Module) through moduleActionContext
(not with destructing), I can't test actions with jest stubs.
How to declare actions with destructed parameters to get code like dispatch.action1
instead of dispatch('action1')
?
I'm receiving the getting wrong action context for modules which are not namespaced.
It is typed correctly, but the actual context at runtime is different than expected.
I'm guessing if it's not namespaced, the dispatch function is the same across the context for non-namespaced modules. Since it is used as a cache key the following line could return the wrong actionContext:
https://github.com/paleo/direct-vuex/blob/master/src/direct-vuex.ts#L164
First of all thanks for your work on this, it is a great looking wrapper for vuex. I'm starting a new app and trying to incorporate server side rendering and am running into a bit of difficult implementing direct-vuex in the pattern defined at https://ssr.vuejs.org/guide/data.html#data-store & https://ssr.vuejs.org/guide/data.html#store-code-splitting
Maybe my inexperience with your library and SSR I'm not seeing a direct solution that would allow me to have a factory function to generate a new store for both the client/server as well as define modules based on the return value of createDirectStore?
When creating modules using defineModule
helper function, the actions and mutations are not categorized in modules. For example:
// What it is:
store.commit.setUsers(users)
store.actions.getUserList()
// What's expected
store.commit.user.setUsers(users)
store.actions.user.getUserList()
I'm using your library in Typescript. Got error:
Module '"~/../node_modules/direct-vuex/types"' has no exported member 'localActionContext'. ts(2305)
Hello I'm using the feathers-vuex Plugin for Vuex. To make it short how it works is that you define multiple plugins for your backend services and these plugins register namespaced modules which can communicate with the feathers backend service.
However since these modules are generated and registered inside a Plugin, I have no Idea how to enable them inside the direct-vuex store and also generate a Typing for them.
I defined my store the following way:
export const {
store,
rootActionContext,
moduleActionContext,
rootGetterContext,
moduleGetterContext,
} = createDirectStore({
strict: debug,
state: {
empty: true,
},
mutations,
actions,
modules: {
desktop,
audio,
connectionStatus,
},
plugins: [
...servicePlugins,
auth,
],
});
servicePlugins
is a list of feathers-vuex plugins, which each register a namespaced module containing getters by using store.registerModule, mutations and actions. My custom defined modules which are defined by using direct-vuex defineModule method are mapped correctly, however the modules defined by the plugins are missing in direct-vuex getters and actions.
Is there a way to add these to direct-feathers to be able to have them types?
I'm just trying to separate typescript declaration code from runtime code, and have a question about direct-vuex configuration from readme: why you don't use d.ts files for declaring module 'vuex'? What am I losing when change code like this:
import { store } from './App.store';
type AppStore = typeof store; // Why export?
declare module 'vuex' {
interface IAppStore<S> {
direct: AppStore;
}
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.