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.
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
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.
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
Related
I'm trying to get user profile information from Auth0. It works in the frontend with the pipe Async
<pre *ngIf="auth.userProfile$ | async as profile">
<code>{{ profile | json }}</code>
</pre>
But in the backend If I want to read the nickname and do something with it. I'm lost.
constructor(public auth: AuthService)
ngOnInit() {
if (this.auth.isAuthenticated$) {
const result = this.auth.userProfile$;
}
}
I know that my varibale "result" is an Observable.
But I'm new with this stuff of Observable. And I try to get the value.
If I use the debug console, I can see the value with this line :
this.auth.userProfile$.source.value.nickname
But If I wrote it in my code, I have this error: Typescript error: Property 'value' does not exist on type 'Observable'
ngOnInit() {
if (this.auth.isAuthenticated$) {
const result = this.auth.userProfile$;
console.log(this.auth.userProfile$.source.value.nickname); // error here
}
}
So someone can help me with this ?
thanks
You access data within an observable by subscribing to it (this is what the async pipe does under the hood).
Here's an example for how to subscribe to your observable:
// import { tap } from 'rxjs/operators';
// tap is one of many rxjs operators
// operators allow you to perform operations on data within observables
this.auth.userProfile$.pipe(
tap(profile => {
console.log(profile);
})
)
.subscribe(); // this is what allows you access the data within the observable
Observables: https://angular.io/guide/observables
RXJS Operators: https://rxjs-dev.firebaseapp.com/guide/operators
I finally find a solution:
in the class I enter this code:
export class getInfo implements OnInit {
private login_info: any;
ngOnInit() {
this.auth.getUser$().subscribe(val => {
this.login_info = val;
console.log('print info', val.nickname);
});
}
}
public useInfo(status: string) {
console.log('print info', this.login_info.nickname);
}
So In the useInfo method, I can use the information that I grabbed from the user profile.
I want to use my vuex modules as classes to make my code more clean and readable. I used the section (Accessing modules with NuxtJS) at the bottom of this document: https://github.com/championswimmer/vuex-module-decorators/blob/master/README.md
I've searched for the solution for almost 3 days and tried out this link:
vuex not loading module decorated with vuex-module-decorators
but, it didn't work.
Also, I used getModule directly in the component like the solution in this issue page: https://github.com/championswimmer/vuex-module-decorators/issues/80
import CounterModule from '../store/modules/test_module';
import { getModule } from 'vuex-module-decorators';
let counterModule: CounterModule;
Then
created() {
counterModule = getModule(CounterModule, this.$store);
}
Then, accessing method elsewhere
computed: {
counter() {
return counterModule.getCount
}
}
it didn't work for me!
This is my Module in store folder in Nuxtjs project:
import { ICurrentUser } from '~/models/ICurrentUser'
import { Module, VuexModule, Mutation, MutationAction } from 'vuex-module-decorators'
#Module({ stateFactory: true, namespaced: true, name: 'CurrentUserStore' })
export default class CurrentUser extends VuexModule {
user: ICurrentUser = {
DisplayName: null,
UserId: null,
};
#Mutation
setUser(userInfo: ICurrentUser) {
this.user = userInfo;
}
get userInfo() {
return this.user;
}
}
In index.ts file in sore folder:
import { Store } from 'vuex'
import { getModule } from 'vuex-module-decorators'
import CurrentUser from './currentUser'
let currentUserStore: CurrentUser
const initializer = (store: Store<any>): void => {
debugger
currentUserStore = getModule(CurrentUser, store)
}
export const plugins = [initializer]
export {
currentUserStore,
}
I think the problem stems from this line:
currentUserStore = getModule(CurrentUser, store)
currentUserStore is created as object but properties and methods are not recognizable.
when I want to use getters or mutation I get error. For instance, "unknown mutation type" for using mutation
Probably several months late but I struggled with a similar issue, and eventually found the solution in https://github.com/championswimmer/vuex-module-decorators/issues/179
It talks about multiple requirements (which are summarised elsewhere)
The one that relates to this issue is that the file name of the module has to match the name you specify in the #Module definition.
In your case, if you rename your file from currentUser to CurrentUserStore' or change the name of the module toCurrentUser`, it should fix the issue.
I have a property selectedOption on the state of my Aurelia Store, which can be changed via actions. I want to observe/subscribe to any changes to this property on the state. My problem is the subscription within the BindingEngine doesn't work because every time you change the state, you create a new copy of the state, therefore the subscription no longer works.
Here is my example:
import { Disposable, BindingEngine, autoinject } from "aurelia-framework";
import { connectTo, dispatchify } from "aurelia-store";
#autoinject()
#connectTo()
export class Holiday {
subscription: Disposable;
state: any;
constructor(private bindingEngine: BindingEngine) {
}
async updateState()
{
await dispatchify(changeSelectedOption)();
}
attached() {
this.subscription = this.bindingEngine
.propertyObserver(this.state, 'selectedOption')
.subscribe((newValue, oldValue) => {
console.log("something has changed!")
});
}
}
export class State {
selectedOption: number = 0;
}
export const changeSelectedOption = (state: State) => {
let updatedState = { ...state };
updatedState.selectedOption++;
return updatedState;
}
store.registerAction("changeSelectedOption", changeSelectedOption);
The first time, my subscription will work and the console will log "something has changed!" as the state is the same object, but it won't work after.
Another solution I could use would be to have a computed property like so:
#computedFrom("state.selectedOption")
get selectedOptionChanged()
{
return console.log("something has changed!");
}
This is a hack, and this computed won't ever be triggered as it is not bound to anything in the HTML.
For context, I want to trigger a server call every time the selectedOption property changes.
What can I do to receive all updates from the property on the state?
The thing here is that the state observable exposed by the Store is a RxJS stream. So with the advent of the new "multi-selector" feature for connectTo you could create two bindings. By implementing a hook called selectorKey Changed, in your sample selectedOptionChanged it would get called on every change of said property.
#connectTo({
selector: {
state: (store) => store.state, // the complete state if you need
selectedOption: (store) => store.state.pluck("selectedOption")
}
})
class MyVM {
...
selectedOptionChanged(newState, oldState) {
// notification about new state
}
}
Instead of store.state.pluck("selectedOption") you can also experiment with additional conditions when to notify about changes like adding distinctUntilChanged etc.
Read more about multi-selectors in the updated docs.
Alternatively if you don't want to use the connectTo decorator, simply use the state property and create another subscription
I have an API class that has various methods that communicate to a backend like "login, register, createPost" etc. I am connecting this class to a reducer. The reducer contains the state of the user info, which I want to be accessible in my Api class:
import axios from 'axios';
import React, { Component } from 'react';
import { connect } from 'react-redux';
#connect(state => ({
api: state.api,
}) )
export default class Api extends Component {
export const login = async({args}) => {
const url = this.props.api.url.concat('/login/');
const config = {
headers: {
'X-CSRFTOKEN': this.props.api.token
}
};
try {
const data = await axios.post(url, {"username": args.username, "password": args.password}, config);
this.props.api.key = data.data.token;
this.props.api.user = data.data.user;
return data;
} catch (e) {
throw e;
}
}
};
async createPost(args (content of the post)) {
try {
const url = this.props.api.url.concat('/post/PostList');
const Response = await axios.post(url, {...args}, !**this.props.api.key**! );
return Response;
} catch (e) {
throw e;
}
}
In the first method, I set the imported state.key and state.user (connected via redux) information, and I want to access that in the second method (this.props.api.key I surrounded by stars). I am trying to do it this way because I have a multitude of actions on different screens, and users have to pass their authentication information to the api method they're calling on top of whatever they're trying to do in order to be able to execute whatever respective action. I figure that it's easier to pass the user info in my Api class instead of importing the Api state into every different file I call the actions in.
The issue I'm running into is I can't instantiate a new object of api like
const api = new Api();
Because it gives me an error "cannot read property store of undefined," so I can't call the actions api.login(withArgs) in respective files, and if I make the methods static they won't have access to this.props.whatever
How do I instantiate a class that's connected to the global state of redux, or how can I access the info in that global state outside of my reducer file?
Since Api extends React.Component, why are you trying to instantiate the class yourself vs. letting React render it for you?
ReactDOM.render(<Api store={store} />)
or if you are not using JSX
ReactDOM.render(React.createElement(Api, { store })
I am trying to use the Auth0 for social login but I keep getting an exception of an undefined reference.
This is the authentication service
import { Injectable } from '#angular/core';
import { tokenNotExpired } from 'angular2-jwt';
// Avoid name not found warnings
declare var Auth0Lock: any;
#Injectable()
export class AuthService {
// Configure Auth0
lock = new Auth0Lock('I have set the ID correctly here', 'and the domain as well', {});
constructor() {
// Add callback for lock `authenticated` event
this.lock.on("authenticated", (authResult) => {
localStorage.setItem('id_token', authResult.idToken);
});
}
public login() {
// Call the show method to display the widget.
this.lock.show();
};
public authenticated() {
// Check if there's an unexpired JWT
// This searches for an item in localStorage with key == 'id_token'
return tokenNotExpired();
};
public logout() {
// Remove token from localStorage
localStorage.removeItem('id_token');
};
}
I injected the services and configured providers. Everything is wired correctly, but it just won't find Auth0Lock even though defined.
Each time it reaches lock = new Auth0Lock('ID', 'DOMAIN', {}); it bombs out.
I replaced declare var Auth0Lock: any; with const Auth0Lock = require('auth0-lock').default; and that fixed the problem.
The accepted answer is good. I did get a Cannot find name 'require' error.
Rather than using 'declare const require', I imported like so:
import Auth0Lock from 'auth0-lock';
I needed to add to index.html:
<script src="https://cdn.auth0.com/js/lock/10.8/lock.min.js"></script>
via https://github.com/auth0/lock/issues/588