Vuex-module-decorator, modifying state inside an action
Asked Answered
C

3

13

Using the vuex-module-decorator I have a authenticate action that should mutate the state.

@Action
public authenticate(email: string, password: string): Promise<Principal> {
    this.principal = null;
    return authenticator
      .authenticate(email, password)
      .then(auth => {
          const principal = new Principal(auth.username);
          this.context.commit('setPrincipal', principal);
          return principal;
      })
      .catch(error => {
          this.context.commit('setError', error);
          return error;
      });
}

// mutations for error and principal

But this fail with the following message:

Unhandled promise rejection Error: "ERR_ACTION_ACCESS_UNDEFINED: Are you trying to access this.someMutation() or this.someGetter inside an @Action? That works only in dynamic modules. If not dynamic use this.context.commit("mutationName", payload) and this.context.getters["getterName"]

What I don't understand is that it works well with @MutationAction and async. However I miss the return type Promise<Principal>.

@MutationAction
public async authenticate(email: string, password: string) {
    this.principal = null;
    try {
        const auth = await authenticator.authenticate(email, password);
        return { principal: new Principal(auth.username), error: null };
    } catch (ex) {
        const error = ex as Error;
        return { principal: null, error };
    }
}

--

At this time I feel blocked and would like to have some help to implement an @Action that can mutate the state and return a specific type in a Promise.

Consubstantial answered 10/1, 2019 at 11:11 Comment(11)
Did you ever find a resolution for this? I'm having the same error...Grackle
Hi Douglas. No, not really. I still cannot have a @MutationAction that returns a well typed Promise. My solution is to use a @MutationAction that defines what will be changed 'mutate={..}'. I don't use the returned promise but react to a change in my store. To good part is that everything is defined into the store. But the bad part is that the effects of an action sent by a component are less clear.Consubstantial
I am hitting something like this too. I have tried with @Action({rawError: true}) and without it. It is my understanding that rawError is supposed to fix this, but for me it works fine in production but I get strange errors in jest unit tests. It seems this package isn't getting much attentionSportswoman
I getting same error and still unable to find solution. Any update @GregVeres?Ario
@Ario I was able to get the code to work in production but not in the unit test and since I believe that unit testing is a vital part of product development, I have abandoned vuex-module-decorator. I am now converting chris fitz's boilerplate to typescript and using that as a starting point.Sportswoman
@Ario If you look for my fork of chris' boilerplate, you can see what I am doing. For vuex I am embracing a javascript approach to modules until Vuex 5 comes out, which is supposed to handle typescript better. Also, minimize the use of vuex.Sportswoman
@Ario I have given up on using vuex. I am using typescript and I am going to use the composition api anyway, so I took a hard look at all the time I was wasting and decided that vuex and typescript don't match. So I choose typescript over vuex. I am now just using classes and a singleton pattern where I need a global. It is much easier and simple to test. with ref() I even retain reactivitySportswoman
@GregVeres do you have some resources describing your pattern; classes and singleton and ref().Consubstantial
@GregVeres Have You tried nuxt-typed-vuex.danielcroe.com ?Ario
@Ario I think I looked at that at one point but then looked at how little it is used on GitHub and decided that I didn't want to lock in the next 5 years of development to an untested package.Sportswoman
@gervais.b, no not yet. But it is quite straight forward. Create a class in its own .ts file. At the bottom of that file create a const variable of the type of the class and new the class. Then everywhere you need access to it, import the file and access the variable.Sportswoman
D
16

Just add rawError option to the annotation so it becomes

   @Action({rawError: true})

And it display error normally. this is because the the library "vuex-module-decorators" wrap error so by doing this you will able to get a RawError that you can work with

Dimarco answered 17/5, 2020 at 5:51 Comment(0)
S
7

You can vote down this answer if you would like because it isn't answering the specific question being posed. Instead, I am going to suggest that if you are using typescript, then don't use vuex. I have spent the past month trying to learn vue /vuex and typescript. The one thing I am committed to is using typescript because I am a firm believer in the benefits of using typescript. I will never use raw javascript again.

If somebody would have told me to not use vuex from the beginning, I would have saved myself 3 of the past 4 weeks. So I am here to try and share that insight with others.

The key is Vue 3's new ref implementation. It is what really changes the game for vuex and typescript. It allows us to not have to rely on vuex to automatically wrap state in a reactive. Instead, we can do that ourselves with the ref construct in vue 3. Here is a small example from my app that uses ref and a typescript class where I was expecting to use vuex in the past.

NOTE1: the one thing you lose when using this approach is vuex dev tools. NOTE2: I might be biased as I am ported 25,000 lines of typescript (with 7000 unit tests) from Knockout.js to Vue. Knockout.js was all about providing Observables (Vue's ref) and binding. Looking back, it was kind of ahead of its time, but it didn't get the following and support.

Ok, lets create a vuex module class that doesn't use vuex. Put this in appStore.ts. To simplify it will just include the user info and the id of the club the user is logged into. A user can switch clubs so there is an action to do that.

export class AppClass {
  public loaded: Ref<boolean>;
  public userId: Ref<number>;
  public userFirstName: Ref<string>;
  public userLastName: Ref<string>;
  // Getters are computed if you want to use them in components
  public userName: Ref<string>;

  constructor() {
    this.loaded = ref(false);
    initializeFromServer()
      .then(info: SomeTypeWithSettingsFromServer) => {
        this.userId = ref(info.userId);
        this.userFirstName = ref(info.userFirstName);
        this.userLastName = ref(info.userLastName);

        this.userName = computed<string>(() => 
          return this.userFirstName.value + ' ' + this.userLastName.value;
        }
     }
      .catch(/* do some error handling here */);
  }

  private initializeFromServer(): Promise<SomeTypeWithSettingsFromServer> {
    return axios.get('url').then((response) => response.data);
  }

  // This is a getter that you don't need to be reactive
  public fullName(): string {
     return this.userFirstName.value + ' ' + this.userLastName.value;
  }

  public switchToClub(clubId: number): Promise<any> {
    return axios.post('switch url')
      .then((data: clubInfo) => {
        // do some processing here
      }
      .catch(// do some error handling here);
  }
}

export appModule = new AppClass();

Then when you want to access appModule anywhere, you end up doing this:

import { appModule } from 'AppStore';

...
if (appModule.loaded.value) {
  const userName = appModule.fullName();
}

or in a compositionApi based component. This is what would replace mapActions etc.

<script lang="ts">
import { defineComponent } from '@vue/composition-api';
import { appModule } from '@/store/appStore';
import footer from './footer/footer.vue';

export default defineComponent({
  name: 'App',
  components: { sfooter: footer },
  props: {},
  setup() {
    return { ...appModule }
  }
});
</script>

and now you can use userId, userFirstName, userName etc in your template.

Hope that helps.

I just added the computed getter. I need to test if that is really needed. It might not be needed because you might be able to just reference fullName() in your template and since fullName() references the .value variables of the other refs, fullName might become a reference itself. But I have to check that out first.

Sportswoman answered 3/4, 2020 at 13:26 Comment(0)
D
0

I sugest this simple solution, work fine for me 👌:

// In SomeClassComponent.vue
import { getModule } from "vuex-module-decorators";
import YourModule from "@/store/YourModule";

someMethod() {
  const moduleStore = getModule(YourModule, this.$store);

  moduleStore.someAction();
}

If the action has parameters, put them. Taken from: https://github.com/championswimmer/vuex-module-decorators/issues/86#issuecomment-464027359

Delitescent answered 5/7, 2022 at 2:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.