I am trying to get vue-router to check a list of permissions, before finishing the navigation to a new route. The permissions are stored in vuex and ideally I'd want to avoid passing them as props everywhere.
When I use the next(vm => {}) callback, regardless of the outcome, it routes to next page, even though it should receive false and cancel the navigation.
beforeRouteEnter(to, undefined, next) {
next(vm => {
const allowedRoles = ['Administrator', 'Contributor'];
if (vm.$store.state.authentication.userInfo.userRoles.some(value => allowedRoles.includes(value))) {
return true;
}
else {
return false;
}
});
}
What am I doing wrong here that is causing it to fail?
By the time you've accessed vm inside next, the navigation has already occurred. Test the value before calling next. You can import the store into the component to access it:
import store from '#/store/index.js'; // import the store
beforeRouteEnter(to, undefined, next) {
const allowedRoles = ['Administrator', 'Contributor'];
const roles = store.state.authentication.userInfo.userRoles;
const isAllowed = roles.some(value => allowedRoles.includes(value))
next(isAllowed); // passes `true` or `false` to `next`
},
This way you can access the store without needing the component instance.
Related
When the vue app is being created, it will dispatch an action from the store that hits the backend endpoint to fetch the user data and update the user state.
I wanted to use navigation guards (either beforeEnter inside the relevant route or beforeRouteEnter inside the component) so it will check if the user data has the right permissions to route to the component. However, when I try to access either store.state or store.getters, it returns the empty data because the router is loaded before the action in the store is completed yet.
I tried different solutions I found online but not much luck yet..The following is a snippet of the code.
export default {
name: 'App',
created() {
this.setupAxios()
this.fetchInitial()
},
methods: {
setupAxios(){
axios.defaults.headers.common['X-CSRFToken'] = window.csrfToken
},
fetchInitial(){
this.$store.dispatch('FETCH_USER_PROFILE')
},
}
}
const actions = {
['FETCH_USER_PROFILE']: ({commit, dispatch}) => {
return axios.get(reverseJS['api2:users-list']())
.then(resp => {
commit('FETCH_USER_PROFILE', resp.data)
return resp.data
})
.catch(error => {
commit('USERS_ERROR', error)
throw error
})
},
}
I am trying to access updated vuex getter from main.js file to check whether user is logged in or not. Based on that i take user to login page. In the main.js file i simply accessing store getter like this.
var router = new VueRouter({...})
router.beforeEach((to, from, next) => {
console.log(store.getters['general/isUserLoggedIn'])
next()
})
general is the module name.
but when simply log store it shows object with updated value. but when logging store.getters['general/isUserLoggedIn'] it shows initial state value. why it is not providing updated value. Is it right way of doing or any alternative way is there.
More details
in store.js file
import Vue from 'vue'
import Vuex from 'vuex'
import general from './modules/general'
import other from './modules/other'
Vue.use(Vuex)
export const store = new Vuex.Store({
modules: {
general,
other
},
plugins: [],
strict: process.env.NODE_ENV !== 'production'
})
checking user logged in or not by calling api status
In App.vue file
axios.get('/api/profile').then((response) => {
if (response.data.status === 'success') {
this.setUserLoggedIn(true)
}
})
actions.js in general module
setUserLoggedIn ({ commit }, data) {
commit(types.SET_USER_LOGGED_IN, data)
}
mutations.js in general module
const mutations = {
[types.SET_USER_LOGGED_IN](state, data) {
state.isUserLoggedIn = data
}
}
On the initial navigation, the login isn't complete yet in the guard, which is why you don't see the value in the console. It only appears true when logging store because it's set shortly after the log, and the console updates itself when you log objects/arrays and then click to view their properties.
One way to fix the race condition is to redirect to the home page when the login is complete:
axios.get('/api/profile').then((response) => {
if (response.data.status === 'success') {
this.setUserLoggedIn(true)
this.$router.push('/'); // Route to home page once login is complete
}
})
And before that, redirect to login if the user is not logged in:
router.beforeEach((to, from, next) => {
const isLoggedIn = store.getters['general/isUserLoggedIn'];
if(!isLoggedIn) {
return next('/login');
}
next()
})
On the initial load, the user will be redirected to the login page.
Once the login completes, they will be sent back to the home page.
I'm using React Native, Firebase and react-navigator.
In the LoadingScreen Component I observe the state changes (onAuthStateChanged)
componentDidMount() {
this.timer = setInterval(
() => firebase.auth().onAuthStateChanged(user => {
user ? this.props.navigation.navigate("App") : this.props.navigation.navigate("Auth");
}),
30,
);
}
In my AuthStack in the ChooseRole Component I have a function in which I want to register a user along with the role to be performed.
if (this.state.role === 'child') {
Firebase
.auth ()
.createUserWithEmailAndPassword (email, password)
.then (() => {
const addChildRole = fc.httpsCallable (('addChildRole'));
addChildRole ({email: this.state.email}). then ((result) =>
this.setState ({role: result}));
})
.catch (errorMessage => {
console.log (errorMessage)
});
})
The problem is that before .then() calls in witch I add a role, the Auth state probably changes and navigates to the application. In the AppStack, the Direction Component, based on the role, I want to target the child or parent component, but by calling
firebase.auth (). CurrentUser.getIdTokenResult ()
.then ((idTokenResult) => {
if (idTokenResult.claims.parent || idTokenResult.claims.child) {
}
idTokenResult.claims.parent and idTokenResult.claims.child gives undefined.
I want to handle giving users the role of registering and logging in, then moving to the appropriate component using Direction.js.
Do you have any idea how to solve this problem?
Not necessarily the cause of your problem, but too long to put in a comment.
There is no need to repeatedly call onAuthStateChanged. You can call it just once, and it will listen to auth state changes from that moment on. Calling it in short intervals as you're doing it bound to give problems.
Do this in componentDidMount instead:
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
user ? this.props.navigation.navigate("App") : this.props.navigation.navigate("Auth");
});
}
What could well be the cause of the problem is indeed that the onAuthStateChanged is fired before your database write completes. You can easily verify this by adding some logging statements.
If it is, you have two main options to fix the problem:
Don't use an onAuthStateChanged listener in componentDidMount, but simply check the value of firebase.auth().currentUser. This does not use a listener, so does not interfere with the database write.
Remove the onAuthStateChanged just before creating the user, so that it doesn't fire. You can do this by keeping the return value from the call to onAuthStateChanged, which is an unsubscribe function. So something along the lines of:
componentDidMount() {
stopAuthStateListener = firebase.auth().onAuthStateChanged(user => {
...
And then:
if (this.state.role === 'child') {
stopAuthStateListener();
Firebase
.auth ()
.createUserWithEmailAndPassword (email, password)
...
I have a login form which needs to re-direct a user to a landing page if the user's email exists in the database.
I have a class called "FormToLogin" with a method called login. In the login method, I dispatch data and this.props.history to an action called loginAct.
Container:
class FormToLogin extends Component {
login = fullForm => {
const { dispatch } = this.props;
const data = {[user]: {...fullForm}}
return dispatch(login(data, this.props.history)) <-- apparently, passing history to an action is not good)
}
}
As you can see, I call the action by passing Data (which will include the email address entered by the user) and the history because I want to make a .push('/new_url') if the email exists in the database.
Action:
export const login = (data, history, dispatch) => {
return api.post(url, data)
.then(({ status, h, data }) => {
// whatever if it returns 200
}
.catch(({ response }) => {
dispatch(loginFail());
const status = (response || {}).status;
if (status === 401 && hasError(error_user, response.data)) {
history.push('/new_url') // This is INCORRECT
?? -- what do I need here -- ??
}
})
}
I have been told that it's bad practice to pass Route history to an Action.
So, history.push() shouldn't happen here.
I've been suggested to add a catch to a container level ("FormToLogin").
So, I've tried to create a catch in the Container(FormToLogin) when I call the Action with dispatch(login(data)), but it doesn't work. Also, var status doesn't exist in the container.
BEFORE: return dispatch(login(data, this.props.history))
AFTER: .catch(e => {
if (status === 401 && hasError(
error_user,
e.response.data
)) {
history.push('/new_url);
} throw e; })
What do I need to add or change?
Two ways to solve this issue.
1) Accessing history object inside Action creator without explicitly passing.
// create history object
history.js
import createHistory from 'history/createHashHistory'
export default createHistory()
action.js
import history from './history'
history.push('/new_url') // use it wherever you want.
2) If you don't want it inside action then handle that inside formLogin.
When dispatching dispatch(loginFail());, inside loginFail function set state of email_address. You could get that state using connect function inside FormToLogin due to react-redux library using props.
Inside render function you could write.
if (this.props.isEmailAddress) { history.push('/new_url') }
I seem to have a weird bug. I'm currently using Redux isomorphically and am also including redux-thunk as the middleware for async actions. Here's what my store config looks like:
// Transforms state date from Immutable to JS
const transformToJs = (state) => {
const transformedState = {};
for (const key in state) {
if (state.hasOwnProperty(key)) transformedState[key] = state[key].toJS();
}
return transformedState;
};
// Here we create the final store,
// If we're in production, we want to leave out development middleware/tools
let finalCreateStore;
if (process.env.NODE_ENV === 'production') {
finalCreateStore = applyMiddleware(thunkMiddleware)(createStore);
} else {
finalCreateStore = applyMiddleware(
createLogger({transformer: transformToJs}),
thunkMiddleware
)(createStore);
}
// Exports the function that creates a store
export default function configureStore(initialState) {
const store = finalCreateStore(reducers, initialState);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('.././reducers/index', () => {
const nextRootReducer = require('.././reducers/index');
store.replaceReducer(nextRootReducer);
});
}
return store;
}
The weird part about this is that I don't think there's anything wrong with this file because my createLogger is applied just fine. It logs out all my actions and state, but the minute I return a function instead of an object in an action creator, the execution is lost. I've tried throwing in debugger statements, which never hit and reordering the middleware also doesn't seem to help.
createUser(data) {
// This `debugger` will hit
debugger;
return (dispatch) => {
// This `debugger` will NOT hit, and any code within the function will not execute
debugger;
setTimeout(() => {
dispatch(
AppActionsCreator.createFlashMessage('yellow', 'Works!')
);
}, 1000);
};
},
Has anyone experienced something like this before?
DOH! I wasn't dispatching the action. I was only calling the action creator. Gonna have to get used to that with Redux!
How I thought I was invoking an action:
AppActionCreators.createFlashMessage('some message');
How to actually invoke an action in Redux:
this.context.dispatch(AppActionCreators.createFlashMessage('some message'));
Where dispatch is a method provided by the Redux store, and can be passed down to every child component of the app through React's childContextTypes