I need to call a mobx action from inside axios interceptor - react-native

When an API returns a 401, I want to force logout the user. The following code is written inside a React Native Ignite project using Mobx
import { useStores } from "../../models"
this.apisauce.axiosInstance.interceptors.response.use(response => {
return response;
}, error => {
if (error.response.status === 401) {
useStores().authStore.reset()
}
return error;
})
The call to authStore.reset() doesn't happen, no error, no console.tron.log, just nothing. Is there a better practice to capture 401 and trigger a mobx action? Without handling it at each individual action that calls into the API?

Related

Intercept network request in Cypress component test

In Cypress e2e test, I was using cy.intercept to intercept a graphql call and make it return mocked response.
Now I am trying to do the same in my Vue component test (mount the component, take some action to make a call to occur) but it seems doing nothing, a call ends up getting null response.
Wonder if I need some setup to make this working? (I'm using Cypress#10.7.0)
const queryName = 'myQuery'
const aliasName = aliasGraphqlName('Query', queryName)
const responseMyQuery = { "data": { ... } }
// beforeEach
cy.intercept('POST', graphqlUrl, (req) => {
aliasGraphqlQuery(req, queryName)
if (req.alias && req.alias === aliasName) {
req.reply(responseMyQuery)
}
})
// my test
cy.mount(MyComponent, {...})
cy.get('[data-test=myField] button.mdi-pencil')
.click() // api call gets null not responseMyQuery
My app was using AWS Cognito for user authentication and to be able to make a graphql call, user should be logged in. For component test, it needs a way to login programmatically.
Cypress has a guide on Amazon Cognito Authentication.
After adding loginByCognitoApi command, my test looks like below and working ok with mocked response:
// component.js
import Amplify, { Auth } from 'aws-amplify'
import awsmobile from '~/aws-exports'
Amplify.configure(awsmobile)
Cypress.Commands.add('loginByCognitoApi', (username, password) => {
...
// beforeEach
cy.intercept('POST', graphqlUrl, (req) => {
aliasGraphqlQuery(req, queryName)
if (req.alias && req.alias === aliasName) {
req.reply(responseMyQuery)
}
})
cy.loginByCognitoApi(
existingUser,
existingUserPassword
)
// my test
cy.mount(MyComponent, {...})
cy.get('[data-test=myField] button.mdi-pencil')
.click() // api call gets responseMyQuery

What are the best practices for handling vuex errors?

I'm new to vue. I use interceptors for handling action responses, all easy with successful responses. But I would like to know what are the best practice to handle error responses.
I want to show a toastr with error message from response by default if there's no catch block in the action, but if there is a catch, do only catch function with no toastr shown.
Also, is it ok to handle unauthorized response making a redirect to login page directly in interceptor and what advices can be given about it?
My current interceptor looks like this:
axios.interceptors.response.use(
(response) => {
return response.data.data;
},
(error: AxiosError) => {
const data = error.response?.data;
const code = data?.code;
if (code === ErrorCodes.NEED_EMAIL_CONFIRMATION) {
router.push("email-verification").then();
} else if (code === ErrorCodes.UNAUTHORIZED) {
router.push("sign-in").then();
} else {
if (undefined !== data.error) {
toaster.error(data.error);
} else {
toaster.error(i18n.t("unexpected"));
}
}
return error;
}
);
but I don't like too many responsibilities here and I don't know how to avoid toastr show when the action has a catch function
You can control error toast notification from where you send the request, by sending an extra config.
Using axios:
axios.post('/api-name', data, {
config: {
showToast: true,
},
})
and then on axios intercept:
axios.interceptors.response.use(
response => {...},
error => {
const showTost= error.config.errorToast
if(showToast){
// show toast you can pass custom message too...<3
}
}

Unable to call another function inside facebook login callback in vue2

I have a login with facebook system on my vue2 application. This captures the access token from FB API and sends to backend for handling user data.
While click on the login with facebook button it fetches access token properly but after that while i am making an axios call this is not getting post method. I tried to put the axios call in another method and call it after fetching token but then it shows that the method is not defined.
Here is my code
async getUserData() {
FB.getLoginStatus(function(response) {
if (response.status === 'connected') {
this.fbLoginSubmit(response.authResponse.accessToken)
}
});
},
fbLoginSubmit(token){
..... axios call here ......
}
Have you tried using arrow (=>) function instead for getLoginStatus?
async getUserData() {
FB.getLoginStatus(response => {
if (response.status === 'connected') {
this.fbLoginSubmit(response.authResponse.accessToken)
}
});
},
fbLoginSubmit(token){
..... axios call here ......
}

How to handle ajax errors reusably with Vuex?

I am building an SPA and I have a couple of different forms that submit data to an API. I am using axios for the ajax calls and have built a wrapper class around it for my use-case, called api. Inside that class I handle errors thrown by that instance.
The problem is I was storing an instance of the api class in each form's state. I later realized that functions shouldn't live in the state due to serialization.
The reasoning behind having the api class in the state was so that all of the children components of a form could access the errors and display their respective error along with removing the error on update.
One solution could be using an axios interceptor and commit all errors to a global errors module. But then I wouldn't know which errors belong to which form, in case two forms (or other requests) were submitted at the same time. I could of course save the errors in regard to the request URI, but then I would also have to take the request method into consideration.
reportError(state, { uri, method, errors }) {
state.errors[uri + '#' + method] = errors;
}
Then I could have a getter like:
getErrorsByRequest: state => ({ uri, method }) => {
return ...
}
But this feels unnecessarily awkward.
Since what I am trying to achieve is most likely very common, I am wondering, how do I sanely handle ajax errors reusably with Vuex?
I was checking for my old projects, and i did something similar:
This is my axios instance interceptor:
axiosInstance.interceptors.response.use(response => response, error => {
const { status } = error.response
...
...
// data invalid (Unprocessable Entity)
if (status === 422) {
// errors list from response
const dataErrors = error.response.data.errors
let objErrors = {}
// joining just the first error from array errors as value for each prop
for (let key in dataErrors) {
objErrors[key] = dataErrors[key].join()
}
// commiting to errors module
store.commit('errors/SET_ERRORS', objErrors)
}
...
...
})
Here my store module errors:
export const state = {
form_errors: {}
}
export const mutations = {
SET_ERRORS: (state, errors) => { state.form_errors = errors },
CLEAN_ERRORS: (state) => { state.form_errors = {} }
}
Using on components:
computed: {
...mapState('errors', ['form_errors'])
}
Using on form template:
<q-input
v-model="form.description"
:error-message="form_errors.description"
:error="!!form_errors.description"
/>

Require cycle while requiring store in axios

In a reactive native application which is using a redux-saga architecture plus axios, I want to intercept 401 requests and dispatch an action which sends me to a login screen.
So in my axios client, I have:
axiosInstance.interceptors.response.use(
(response) => {
return response
},
(error) => {
// token expired
if (error.response.status === 401) {
console.log(`401 interceptor error: ${JSON.stringify(error)}`)
store.dispatch({type: "UNAUTHORIZED_RESPONSE_RECEIVED"})
}
return Promise.reject(error)
}
)
Now, while this works, the problem is I am having a require cycle:
Require cycle: redux/store.js -> redux/sagas.js -> redux/project/saga.js -> helpers/projectHelper.js -> helpers/client.js -> redux/store.js
This is obvious, but since to create the store I am applying the sagaMiddleware, to define it I import my sagas, in which I import the projectHelper file (which is a series of axios ajax api calls) in which I import the client which, to be able to perform the store.dispatch() needs to import the store, following the option no.1 from this series of options:
https://daveceddia.com/access-redux-store-outside-react/#option-1-export-the-store
Everything works, but this warning worries me a little bit.
Require cycles are allowed, but can result in uninitialized values. Consider refactoring to remove the need for a cycle.
My question is: how could I find other (also creative) ways to achieve what I need, which is:
intercept the 401 (not putting it into every saga action that fails)
(optional) dispatch an action which ends up ->
sending me to the "Login" Screen?
For anyone having trouble with this use case, this was the solution I adopted.
In one of my app main components (might be App.tsx), I put an Axios interceptor
componentDidMount() {
const self = this;
axios.interceptors.request.use(
function(config: AxiosRequestConfig) {
// useful to show a loader
self.props.loading(true);
return config;
},
function(error) {
return Promise.reject(error);
}
);
axios.interceptors.response.use(
function(response) {
// loader stops
self.props.loading(false);
return response;
},
function(error) {
self.props.loading(false);
if (
typeof error.response !== "undefined" &&
error.response.status === 401
) {
console.log(`401 interceptor error: ${JSON.stringify(error)}`);
NavigationService.navigate("Login", null);
}
if (
typeof error.message !== "undefined" &&
error.message == "Network Error"
) {
console.log(`Network Error`);
}
return Promise.reject(error);
}
);
Not perfect, but I hope it could be useful for people trying to achieve this!