How can I handle a vuex dispatch response? - javascript

I'm raising the white flag and asking for suggestions even though I feel the answer is probably right in front of my face.
I have a login form that I am submitting to an api (AWS) and acting on the result. The issue I am having is once the handleSubmit method is called, I am immediately getting into the console.log statement... which to no surprise returns dispatch result: undefined
I realize this is likely not a direct function of vue.js, but how I have the javascript set executing.
Here is my login component:
// SignInForm.vue
handleSubmit() {
try {
const {username, password} = this.form;
this.$store.dispatch('user/authenticate', this.form).then(res => {
console.log('dispatch result: ', res);
});
} catch (error) {
console.log("Error: SignInForm.handleSubmit", error);
}
},
...
Here is what my store is doing. I'm sending it to a UserService I've created. Everything is working great. I am getting the correct response(s) and can log everything out I need. The UserService is making an axios request (AWS Amplify) and returning the response.
// user.js (vuex store)
authenticate({state, commit, dispatch}, credentials) {
dispatch('toggleLoadingStatus', true);
UserService.authenticate(credentials)
.then(response => {
dispatch('toggleLoadingStatus', false);
if (response.code) {
dispatch("setAuthErrors", response.message);
dispatch('toggleAuthenticated', false);
dispatch('setUser', undefined);
// send error message back to login component
} else {
dispatch('toggleAuthenticated', true);
dispatch('setUser', response);
AmplifyEventBus.$emit("authState", "authenticated");
// Need to move this back to the component somehow
// this.$router.push({
// name: 'dashboard',
// });
}
return response;
});
},
...
Where I'm getting stuck at is, if I have error(s) I can set the errors in the state, but I'm not sure how to access them in the other component. I've tried setting the data property to a computed method that looks at the store, but I get errors.
I'm also struggling to use vue-router if I'm successfully authenticated. From what I've read I really don't want to be doing that in the state anyway -- so that means I need to return the success response back to the SignInForm component so I can use vue-router to redirect the user to the dashboard.

Yep. Just took me ~6 hours, posting to SO and then re-evaluating everything (again) to figure it out. It was in fact, somewhat of a silly mistake. But to help anyone else here's what I was doing wrong...
// SignInForm.vue
async handleSubmit() {
try {
await this.$store.dispatch("user/authenticate", this.form)
.then(response => {
console.log('SignInForm.handleSubmit response: ', response); // works
if (response.code) {
this.errors.auth.username = this.$store.getters['user/errors'];
} else {
this.$router.push({
name: 'dashboard',
});
}
}).catch(error => {
console.log('big problems: ', error);
});
} catch (error) {
console.log("Error: SignInForm.handleSubmit", error);
}
},
...
Here's my first mistake: I was calling from an async method to another method - but not telling that method to be async so the call(er) method response was executing right away. Here's the updated vuex store:
// user.js (vuex store)
async authenticate({state, commit, dispatch}, credentials) { // now async
dispatch('toggleLoadingStatus', true);
return await UserService.authenticate(credentials)
.then(response => {
console.log('UserService.authenticate response: ', response); // CognitoUser or code
dispatch('toggleLoadingStatus', false);
if (response.code) {
dispatch("setAuthErrors", response.message);
dispatch('toggleAuthenticated', false);
dispatch('setUser', undefined);
} else {
dispatch('toggleAuthenticated', true);
dispatch('setUser', response);
AmplifyEventBus.$emit("authState", "authenticated");
}
return response;
});
},
...
My second error was that I wasn't returning the result of the method at all from the vuex store.
Old way:
UserService.authenticate(credentials)
Better way:
return await UserService.authenticate(credentials)
Hope this saves someone a few hours. ¯_(ツ)_/¯

This works for Vue3:
export default {
name: 'Login',
methods: {
loginUser: function () {
authenticationStore.dispatch("loginUser", {
email: 'peter#example.com',
})
.then(response => {
if (response.status === 200) {
console.log('Do something')
}
});
},
},
}
In the store you can simply pass back the http response which is a promise.
const authenticationStore = createStore({
actions: {
loginUser({commit}, {email}) {
const data = {
email: email
};
return axios.post(`/authentication/login/`, data)
.then(response => {
toastr.success('Success')
return response
})
},
}
})

Related

axios get request not working inside useEffect

I am using axios get request to check if user logged in or not with jwt, However, when app launched it keeps showing the loading state is it set to true in the first time the app launch then it supposes to make get request and validate the user then set loading state to false and navigate to a specific route. what i am getting is loading state is true all time and request not send to backend server.
here is the function to check if user logged in or not:
useEffect(() => {
const checkLoggedIn = async () => {
const Token = await AsyncStorage.getItem('Token');
if (Token) {
axios.get('http://localhost:3000/isUserAuth', {
headers: {
'x-access-token': Token
}
}).then((res) => {
setUser(res.data.user)
AsyncStorage.setItem("Token", res.token);
setIsLoading(false);
}).catch((error) => {
console.log(error)
setIsLoading(false);
});
} else {
setUser(null);
setIsLoading(false)
}
}
checkLoggedIn();
}, []);
and this is the backend:
app.get('/isUserAuth', verifyJWT, (req, res) => {
const token = req.headers['x-access-token'];
let sqlCheck = `SELECT * FROM users where id =?`;
CON.query(sqlCheck, req.user, (err, user) => {
if (user) {
console.log(user)
return res.status(400).json({ auth: true, user: user, Token: token })
}
})
})
Hope someone help me identifying the problem. thanks
If your loading state is not changing to false that signals to me that it's not a problem with your database call because even if your call is failing the else should trigger and still set loading to false.
Might consider narrowing down the function complexity and building up from there. Maybe something like the following to make sure your loading state is correctly updating:
useEffect(() => {
const checkLoggedIn = async () => {
setUser(null);
setIsLoading(false)
}
checkLoggedIn();
}, []);
What about setIsLoading to true when the component mounts, then run the async/await based on its value?
useEffect(() => {
setIsLoading(false);
if (!setIsLoading) {
try {
// fetch code
} catch(e) {
//some error handling
} finally {
// something to do whatever the outcome
}
}
})
// I found the solution
the problem is from the backend server where user returns undefined and the mistake i made that only check if(user) and didn't set else which not give a response back to font-end which indicate that state keep true
so backend code should be like:
CON.query(sqlCheck, req.user, (err, user) => {
if (user) {
return res.status(200).json({ auth: true, user: user, Token: token })
}else{ return res.status(400).json({ auth: false, user: null })}
})

Nuxt and Vue Apollo. How to handle errors inside Smart Query with redirection to 404/400/500 error page? Can we catch such errors?

With regular HTTP request, we may create redirection with asyncData({error}){...}
What should we use for redirecting to 400 page using Smart Query?
With Vue Apollo, I am trying to use
apollo: {
queryName: {
prefetch: true,
query: wrongQuery,
error(errorData) {
this.$nuxt.error({
statusCode: 500,
message: 'Error message',
});
},
},
};
In case if we reload the page, redirection doesn't work. We still got an error becouse server side rendering:
With global error handler like:
// /plugins/apollo-error-handler.js
export default ({ graphQLErrors, networkError, operation, forward }, nuxtContext) => {
console.log(networkError)
nuxtContext.error({
statusCode: 404,
message: 'Error message',
});
};
Only errors logging works. Redirection doesn't work at all.
Do we have any way to handle errors inside smart queries with redirection to 400 page for example?
Can we catch such errors in smart query? Like try...catch... in asyncData() to prevent app crash.
I found the solution here!
export default function ({ redirect }) {
const link = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.map(({ message }) => {
if (`${message}` === 'Unauthenticated.') {
redirect('/login')
// Do Something
localStorage.setItem('logged', false)
}
})
}
if (networkError) {
console.log(`[Network error]: ${networkError}`)
}
})
return {
defaultHttpLink: false,
link: ApolloLink.from([link, createHttpLink({
credentials: 'include',
uri: 'http://localhost:8000/graphql',
fetch: (uri, options) => {
options.headers['X-XSRF-TOKEN'] = Cookies.get('XSRF-TOKEN')
return fetch(uri, options)
}
})]),
cache: new InMemoryCache()
}
}`
Hopefully this answer helpful for you!
Cheers!
The smart query like this is pretty limited and so, I prefer to handle it in my Vuex store. Not sure if it's the best practice but it works great for me right now.
async niceFancyVuexAction({ dispatch }, { fancyInput }) {
try {
const { errors, data } = await this.app.apolloProvider.defaultClient.mutate({
mutation: yourGraphqlMutationHere,
errorPolicy: 'all',
variables: {
input: fancyInput,
},
})
if (errors) {
return dispatch('handleErrors', 'we got some errors')
}
dispatch('anotherNiceFancyVuexAction', data.Address.createForCompany)
console.log('success !')
} catch (error) {
// here, you could catch the error and maybe make a redirect
dispatch('handleErrors', 'the call was not successfull')
}
},
Otherwise, yeah using the onError link is also a good idea if you feel configuring it: https://www.apollographql.com/docs/react/data/error-handling/

VueX/VueJs : Execute code in component after async process

I'm trying to display a toast when a async request is finished.
I've implemented this process:
Single File Component calls updateUserProfile() actions in my VueX store
updateUserProfile() actions makes a outgoing HTTP request on a server using Axios
When succeeded, I use a mutation to update the user profile in my store and i would like to show a toast from my single file component.
Problem is that the response object is always undefined in my component. Where is my mistake ?
Error :
profile.vue?a62a:328 Uncaught (in promise) TypeError: Cannot read
property 'data' of undefined
at eval (profile.vue?a62a:328)
Store:
/*
* Action used to fetch user data from backend
*/
updateUserProfile ({commit, state}, userData) {
// Inform VueX that we are currently loading something. Loading spinner will be displayed.
commit('SET_IS_LOADING', true);
axiosBackend.put('/user/profile', userData, { headers: { Authorization: state.authString } } ).then(res => {
console.log('PUT /user/profile', res);
// Set user Data in VueX Auth store
commit('SET_USER_DATA', {
user: res.data.data
});
// Reset is Loading
commit('SET_IS_LOADING', false);
return res.data;
})
.catch(error => {
// Reset isLoading
commit('SET_IS_LOADING', false);
});
}
Component:
methods: {
// mix the getters into computed with object spread operator
...mapActions([
'updateUserProfile'
]),
// Function called when user click on the "Save changes" btn
onSubmit () {
console.log('Component(Profile)::onSaveChanges() - called');
const userData = {
firstName: this.firstname,
}
this.updateUserProfile(userData).then( (response) => {
console.log('COMPONENT', response);
if (response.data.status === 200) {
toastr.success("Your profile has been successfully updated.");
}
});
}
}
Well,
It would be better idea if You trigger the toast from the Vuex store itself as mentioned below.
callAddToCart: ({ commit }, payload) => {
axiosBackend.put('/user/profile', userData, { headers: { Authorization:
state.authString }}).then(response => {
commit("setLoading", false, { root: true });
payload.cartKey = response.key;
commit("setNotification", {
type: 'success',
title: `title`,
});
commit("ADD_TO_CART", payload);
});
},
and inside mutation you can have a general notification toast and you can pass type, message and title as below.
setNotification(state, {type, message, title}) {
state.flash = {
type,
title,
message
}
}
NOTE: Do not forget to load toast element at the root level in order to display in the UI.
Here is working example
Hope this helps!

How can you use axios interceptors?

I have seen axios documentation, but all it says is
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
Also many tutorials only show this code but I am confused what it is used for, can someone please give me simple example to follow.
To talk in simple terms, it is more of a checkpoint for every HTTP action. Every API call that has been made, is passed through this interceptor.
So, why two interceptors?
An API call is made up of two halves, a request, and a response. Since it behaves like a checkpoint, the request and the response have separate interceptors.
Some request interceptor use cases -
Assume you want to check before making a request if your credentials are valid. So, instead of actually making an API call, you can check at the interceptor level that your credentials are valid.
Assume you need to attach a token to every request made, instead of duplicating the token addition logic at every Axios call, you can make an interceptor that attaches a token on every request that is made.
Some response interceptor use cases -
Assume you got a response, and judging by the API responses you want to deduce that the user is logged in. So, in the response interceptor, you can initialize a class that handles the user logged in state and update it accordingly on the response object you received.
Assume you have requested some API with valid API credentials, but you do not have the valid role to access the data. So, you can trigger an alert from the response interceptor saying that the user is not allowed. This way you'll be saved from the unauthorized API error handling that you would have to perform on every Axios request that you made.
Here are some code examples
The request interceptor
One can print the configuration object of axios (if need be) by doing (in this case, by checking the environment variable):
const DEBUG = process.env.NODE_ENV === "development";
axios.interceptors.request.use((config) => {
/** In dev, intercepts request and logs it into console for dev */
if (DEBUG) { console.info("✉️ ", config); }
return config;
}, (error) => {
if (DEBUG) { console.error("✉️ ", error); }
return Promise.reject(error);
});
If one wants to check what headers are being passed/add any more generic headers, it is available in the config.headers object. For example:
axios.interceptors.request.use((config) => {
config.headers.genericKey = "someGenericValue";
return config;
}, (error) => {
return Promise.reject(error);
});
In case it's a GET request, the query parameters being sent can be found in config.params object.
The response interceptor
You can even optionally parse the API response at the interceptor level and pass the parsed response down instead of the original response. It might save you the time of writing the parsing logic again and again in case the API is used in the same way in multiple places. One way to do that is by passing an extra parameter in the api-request and use the same parameter in the response interceptor to perform your action. For example:
//Assume we pass an extra parameter "parse: true"
axios.get("/city-list", { parse: true });
Once, in the response interceptor, we can use it like:
axios.interceptors.response.use((response) => {
if (response.config.parse) {
//perform the manipulation here and change the response object
}
return response;
}, (error) => {
return Promise.reject(error.message);
});
So, in this case, whenever there is a parse object in response.config, the manipulation is done, for the rest of the cases, it'll work as-is.
You can even view the arriving HTTP codes and then make the decision. For example:
axios.interceptors.response.use((response) => {
if(response.status === 401) {
alert("You are not authorized");
}
return response;
}, (error) => {
if (error.response && error.response.data) {
return Promise.reject(error.response.data);
}
return Promise.reject(error.message);
});
You can use this code for example, if you want to catch the time that takes from the moment that the request was sent until the moment you received the response:
const axios = require("axios");
(async () => {
axios.interceptors.request.use(
function (req) {
req.time = { startTime: new Date() };
return req;
},
(err) => {
return Promise.reject(err);
}
);
axios.interceptors.response.use(
function (res) {
res.config.time.endTime = new Date();
res.duration =
res.config.time.endTime - res.config.time.startTime;
return res;
},
(err) => {
return Promise.reject(err);
}
);
axios
.get("http://localhost:3000")
.then((res) => {
console.log(res.duration)
})
.catch((err) => {
console.log(err);
});
})();
It is like a middle-ware, basically it is added on any request (be it GET, POST, PUT, DELETE) or on any response (the response you get from the server).
It is often used for cases where authorisation is involved.
Have a look at this: Axios interceptors and asynchronous login
Here is another article about this, with a different example: https://medium.com/#danielalvidrez/handling-error-responses-with-grace-b6fd3c5886f0
So the gist of one of the examples is that you could use interceptor to detect if your authorisation token is expired ( if you get 403 for example ) and to redirect the page.
I will give you more practical use-case which I used in my real world projects. I usually use, request interceptor for token related staff (accessToken, refreshToken), e.g., whether token is not expired, if so, then update it with refreshToken and hold all other calls until it resolves. But what I like most is axios response interceptors where you can put your apps global error handling logic like below:
httpClient.interceptors.response.use(
(response: AxiosResponse) => {
// Any status code that lie within the range of 2xx cause this function to trigger
return response.data;
},
(err: AxiosError) => {
// Any status codes that falls outside the range of 2xx cause this function to trigger
const status = err.response?.status || 500;
// we can handle global errors here
switch (status) {
// authentication (token related issues)
case 401: {
return Promise.reject(new APIError(err.message, 409));
}
// forbidden (permission related issues)
case 403: {
return Promise.reject(new APIError(err.message, 409));
}
// bad request
case 400: {
return Promise.reject(new APIError(err.message, 400));
}
// not found
case 404: {
return Promise.reject(new APIError(err.message, 404));
}
// conflict
case 409: {
return Promise.reject(new APIError(err.message, 409));
}
// unprocessable
case 422: {
return Promise.reject(new APIError(err.message, 422));
}
// generic api error (server related) unexpected
default: {
return Promise.reject(new APIError(err.message, 500));
}
}
}
);
How about this. You create a new Axios instance and attach an interceptor to it. Then you can use that interceptor anywhere in your app
export const axiosAuth = axios.create()
//we intercept every requests
axiosAuth.interceptors.request.use(async function(config){
//anything you want to attach to the requests such as token
return config;
}, error => {
return Promise.reject(error)
})
//we intercept every response
axiosAuth.interceptors.request.use(async function(config){
return config;
}, error => {
//check for authentication or anything like that
return Promise.reject(error)
})
Then you use axiosAuth the same way you use axios
This is the way I used to do in my project. The code snippet refers how to use access and refresh token in the axios interceptors and will help to implements refresh token functionalities.
const API_URL =
process.env.NODE_ENV === 'development'
? 'http://localhost:8080/admin/api'
: '/admin-app/admin/api';
const Service = axios.create({
baseURL: API_URL,
headers: {
Accept: 'application/json',
},
});
Service.interceptors.request.use(
config => {
const accessToken = localStorage.getItem('accessToken');
if (accessToken) {
config.headers.common = { Authorization: `Bearer ${accessToken}` };
}
return config;
},
error => {
Promise.reject(error.response || error.message);
}
);
Service.interceptors.response.use(
response => {
return response;
},
error => {
let originalRequest = error.config;
let refreshToken = localStorage.getItem('refreshToken');
const username = EmailDecoder(); // decode email from jwt token subject
if (
refreshToken &&
error.response.status === 403 &&
!originalRequest._retry &&
username
) {
originalRequest._retry = true;
return axios
.post(`${API_URL}/authentication/refresh`, {
refreshToken: refreshToken,
username,
})
.then(res => {
if (res.status === 200) {
localStorage.setItem(
'accessToken',
res.data.accessToken
);
localStorage.setItem(
'refreshToken',
res.data.refreshToken
);
originalRequest.headers[
'Authorization'
] = `Bearer ${res.data.accessToken}`;
return axios(originalRequest);
}
})
.catch(() => {
localStorage.clear();
location.reload();
});
}
return Promise.reject(error.response || error.message);
}
);
export default Service;
I have implemented in the following way
httpConfig.js
import axios from 'axios'
import { baseURL } from '../utils/config'
import { SetupInterceptors } from './SetupInterceptors'
const http = axios.create({
baseURL: baseURL
})
SetupInterceptors(http)
export default http
SetupInterceptors.js
import { baseURL } from '../utils/config'
export const SetupInterceptors = http => {
http.interceptors.request.use(
config => {
config.headers['token'] = `${localStorage.getItem('token')}`
config.headers['content-type'] = 'application/json'
return config
},
error => {
return Promise.reject(error)
}
)
http.interceptors.response.use(function(response) {
return response
}, function (error) {
const status = error?.response?.status || 0
const resBaseURL = error?.response?.config?.baseURL
if (resBaseURL === baseURL && status === 401) {
if (localStorage.getItem('token')) {
localStorage.clear()
window.location.assign('/')
return Promise.reject(error)
} else {
return Promise.reject(error)
}
}
return Promise.reject(error)
})
}
export default SetupInterceptors
Reference : link

ReactJS - Props not available immediately inside componentDidMount on a refresh

Having a frustrating issue, hope someone can help. for full repo available at https://github.com/georgecook92/Stir/blob/master/src/components/posts/viewPosts.jsx.
Get straight into the code -
componentDidMount() {
const {user_id,token} = this.props.auth;
this.props.startLoading();
console.log('props auth', this.props.auth);
if (user_id) {
console.log('user_id didMount', user_id);
this.props.getUserPosts(user_id, token);
}
}
If the component is loaded through the ui from another component it functions as expected. However, if the page is refreshed user_id etc is not available immediately to componentDidMount.
I have checked and it is available later on, but I found that if I move my AJAX call to get the posts to a render method or other lifecycle method like componentWillReceiveProps - the props are being updated constantly and it locks the UI - not ideal.
I am also unsure as to why multiple ajax calls per second are being made if I move the ajax call to the render method.
I hope you can help! Thank you.
Edit.
export function getUserPosts(user_id, token){
return function(dispatch) {
if (window.indexedDB) {
var db = new Dexie('Stir');
db.version(1).stores({
posts: '_id, title, user_id, text, offline',
users: 'user_id, email, firstName, lastName, token'
});
// Open the database
db.open().catch(function(error) {
alert('Uh oh : ' + error);
});
db.posts.toArray().then( (posts) => {
console.log('posts:', posts);
if (posts.length > 0) {
dispatch( {type: GET_POSTS, payload: posts} );
}
});
}
axios.get(`${ROOT_URL}/getPosts?user_id=${user_id}`, {
headers: {
authorisation: localStorage.getItem('token')
}
}).then( (response) => {
console.log('response from getPosts action ', response);
dispatch( {type: GET_POSTS, payload: response.data} );
dispatch(endLoading());
response.data.forEach( (post) => {
if (post.offline) {
if (window.indexedDB) {
db.posts.get(post._id).then( (result) => {
if (result) {
//console.log('Post is already in db', post.title);
} else {
//console.log('Post not in db', post.title);
//useful if a posts offline status has changed
db.posts.add({
_id: post._id,
title: post.title,
user_id: post.user_id,
text: post.text,
offline: post.offline
});
}
} )
}
}
} );
})
.catch( (err) => {
console.log('error from get posts action', err);
if (err.response.status === 503) {
dispatch(endLoading());
dispatch(authError('No internet connection, but you can view your offline posts! '));
} else {
dispatch(endLoading());
dispatch(authError(err.response.data.error));
}
});
}
}
I would suggest using componentWillReceiveProps and saving your props as state to trigger a re-render if a change occurs (ex. a prop changes from undefined to having a value). Something like this will work:
import React, {Component} from 'react';
// using lodash for checking equality of objects
import _isEqual from 'lodash/isEqual';
class MyComponent extends Component {
constructor(props={}) {
super();
this.state = props;
}
// when the component receives new props, like from an ajax request,
// check to see if its different from what we have. If it is then
// set state and re-render
componentWillReceiveProps(nextProps) {
if(!_isEqual(nextProps, this.state)){
this.setState(nextProps);
}
}
render() {
return (
<div>{this.state.ItemPassedAsProp}</div>
);
}
}

Categories

Resources