Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vuex-module-decorator, modifying state inside an action

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.

like image 873
gervais.b Avatar asked Jan 10 '19 11:01

gervais.b


2 Answers

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

like image 94
mwangaben Avatar answered Oct 02 '22 01:10

mwangaben


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.

like image 36
Greg Veres Avatar answered Oct 02 '22 00:10

Greg Veres