Axios not catching Network Offline - react-native

Challenge is to catch network offline status error during POST request using AXIOS library in React-Native.
axios.post(Constants.API.LOGIN, {
username: email,
password: password
})
.then(function (response) {
console.log('SUCCESS!');
loginUserSuccess(dispatch, user);
})
.catch(function (error) {
if(!error.response){
networkError(dispatch);
} else {
loginUserFail(dispatch);
}
});
but when I switch off WiFi getting error
Possible Unhandled Promise Rejection (id:0)
What is the way to handle network statuses with AXIOS?
Thanks!

Check out NetInfo:
import { NetInfo } from 'react-native'
https://facebook.github.io/react-native/docs/netinfo.html
isConnected
Available on all platforms. Asynchronously fetch a boolean to determine internet connectivity.
NetInfo.isConnected.fetch().then(isConnected => {
console.log('First, is ' + (isConnected ? 'online' : 'offline'));
});
function handleFirstConnectivityChange(isConnected) {
console.log('Then, is ' + (isConnected ? 'online' : 'offline'));
NetInfo.isConnected.removeEventListener(
'change',
handleFirstConnectivityChange
);
}
NetInfo.isConnected.addEventListener(
'change',
handleFirstConnectivityChange
);
You may have a bit of extra issue there with your handling. I can't see exactly what is triggering the unhandled rejection in your code, but I can assume it is making it into the catch block.
If so, that currently means that either networkError() or loginUserFail() are failing, which is what is actually generating the unhandled rejection.
Take a look at those functions and make sure they have catch blocks inside them if they are asynchronous. Ask yourself where exactly is that error coming from, and answer that first, then look at NetInfo. Otherwise, the unhandled rejection may still be able to occur. Make sure you test all the different ways the code could fail :)
With NetInfo, you could set up something near the root-level in your app architecture, by way of that event listener. This could allow you to manage a setting in something like Redux. If the app detects offline, you could set the value to false and the entire app would know it is offline. You could probably make a middleware.
The code might look something like:
middleware:
`store.dispatch(changeConnectivityState())`
or you could have an action creator (shown with redux-thunk):
export function onAppConnectivityChange(newState) {
return (dispatch) {
dispatch({
type: APP_CONNECTIVITY_CHANGE,
payload: newState
})
}
}
Redux reducer:
case APP_CONNECTIVITY_CHANGE:
return {
...state,
isConnected: action.payload
}
In your components:
render() {
if (this.props.isConnected === true) {
return <View><Text>We are connected.</Text></View>
}
}
const mapStateToProps = (state) => {
const isConnected = state.someReducer.isConnected
return {
isConnected
}
}
export default connect(mapStateToProps, null)(SomeComponent)
Hopefully that gives you some ideas. Axios shouldn't be responsible for checking if the app is online or offline (except for quickly checking against a boolean). Your app should always know if it online or offline, and your components should be able to detect the state. You will find that much more scalable and easier to work with.
If I saw where you were calling Axios from, I could give you a more specific answer.

Related

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
}
}

Enzyme integration testing: axios.get call not being executed in redux-saga

I am trying to setup tests for some an action creator that is triggering a redux saga.
My saga retrieves a word from a local flask server (will always return the same word) and then displays that word. This is not my real-life case but I tried to start with something easy...
My action creator and saga work as expected when I trigger them with a button in my react app (the word is retrieved from the server, stored in my redux store and the displayed with a selector in my react component), but I cannot get the test to succeed.
I would like to test only the redux part, not the actual rendered react component (not sure if that is part of my problem or not)
I use Enzyme for tests, my store is created correctly and can dispatch the action. I can also see that my saga is being called with the console logs:
My test code:
import { Store } from 'redux';
import { RootState } from '../root.reducer';
import { storeFactory } from '../../../test/testUtils';
import { getSecretWord } from './secret-word.actions';
describe('getSecretWord action creator', () => {
let store: Store<RootState>;
beforeEach(() => {
store = storeFactory();
});
test('add response word to state', () => {
const secretWord = 'party';
store.dispatch(getSecretWord());
const newState = store.getState();
console.log('new state: ' + newState.secretWord);
expect(newState.secretWord).toBe(secretWord);
});
});
and my saga function:
export function* getSecretWordSaga(action: getSecretWordAction): Generator<ForkEffect | CallEffect | PutEffect, void, unknown>
{
try {
console.log('getSecretWordSaga() saga started');
console.log('before axios query call:');
const response:any = yield call(api.get, '/api/word');
// const response = {data: { word: 'party'}, status:200}
console.log('axios query returned: ');
console.log(response);
yield put(setSecretWord(response.data.word));
console.log('getSecretWordSaga() saga finsshed');
} catch (err) {
console.log('error occured:');
console.log(err);
console.log('getSecretWordSaga() saga finsshed with errors');
}
}
export function* getSecretWordSagaStart(): Generator<
ForkEffect<never>,
void,
unknown
> {
yield takeLatest(SecretWordActionTypes.GET_SECRET_WORD, getSecretWordSaga);
}
The axios api is very basic and it includes two interceptors for logging purposes:
import axios from 'axios';
export const api = axios.create({
baseURL: 'http://localhost:5000',
responseType: 'json',
});
api.interceptors.request.use(request => {
console.log('Starting Request', JSON.stringify(request, null, 2))
return request
})
api.interceptors.response.use(response => {
console.log('Response:', JSON.stringify(response, null, 2))
return response
})
I can see in the logs (in "npm test") that I get log for the line "before axios query call:' and one console.log for the request interceptor (everything looks fine there), but no more logs afterwards (neither success nor error)
If I comment out the "yield call.." and hardcode the response (like in the commented out line below), my saga runs through the end and my test succeeds.
Why is the yield Call(api.get, '/api/word') not being executed (and I don't get any error message)?
The code is my opinion correct as it is running fine when executed in react. My flask server is obviously also running and I can see in the flask app than no call to the api are being made by the running tests.
I obviously plan to mock that api call but was also running into some problems there, that's why I first wanted to get the real api call working.
After trying many different ways for adding a timeout, setting the testing function to async and adding a setTimeout in a promise did work.
It's not ideal as I have to set the timeout to a specific value, but I could not figure out a better way to get it working.
test("add response word to state", async () => {
const secretWord = 'party';
store.dispatch(getSecretWord());
await new Promise(res => setTimeout(res, 1000));
const newState = store.getState();
console.log('new state: ' + newState.secretWord);
expect(newState.secretWord).toBe(secretWord);
});

How to prevent error propagation in Apollo Client useQuery in react?

I'd like to catch the error in component level and prevent propagation while using the useQuery in #apollo/react-hook.
Here is my example code
const invitationDocument = gql`
query DecodeInvitation($token: String!) {
DecodeInvitation(token: $token) {
name
email
}
}
`
const InvitationPage = (props) => {
const { data, error, loading } = useQuery(invitationDocument, {variables: { token: "XXXX" }});
if(error)
{
return <InvitationErrorPage error={error.message}/>
}
return loading? <LoadingPage> : <InvitationAcceptPage />
}
It works fine but at the same time, the error is being propagated to its parents level so I get another error notification message which comes from the error handler at the global level.
At the application level, I use the apollo-link-error to manage the Graphql errors.
import { onError } from 'apollo-link-error';
const errorLink = onError (({ graphqlErrors, networkError }) => {
if(graphqlErrors)
notification.error(graphqlErrors[0].message);
});
const client = ApolloClient({
cache: new InMemoryCache(),
link: ApolloLink.from([
errorLink,
new HttpLink({ uri: `http://localhost:8080/graphql`})
])
})
For now, I am finding a solution to stop propagation to top-level so that I can show only InvitationErrorPage and stop displaying error notification at the global level.
I was also trying to prevent errors from being logged in an Error Link by handling them on a useQuery hook and a further delve into the ApolloLink documentation helped clear up what is happening. The key misunderstanding is that the Error Link is not a parent- or application-level handler, it is request middleware. It's helpful to think about how the data is coming back from the server:
Thus, when you see an error notification from the Error Link it is not something that "propagated up" from the useQuery hook: it occurred in the request path before the useQuery result was available on the client.
Thus, the onError callback for the Error Link will always be called before any error handling code in the useQuery hook.
Probably your best bet is to use a combination of the operation and graphQLErrors[x].extensions to figure out what errors you should pass through the Error Link middleware like so:
const errorLink = onError(({operation, response, graphQLErrors}) => {
if (!graphQLErrors) {
return;
}
if (operation.operationName === "DecodeInvitation") {
for (const err of graphQLErrors) {
if (err.extensions?.code === 'UNAUTHENTICATED') {
// Return without "notifying"
return;
}
}
}
// Notify otherwise
notification.error(graphqlErrors[0].message);
})

Vuex, best practice with a global errors and notifications handling

here is what i do, and i'am not realy sure its correct :
//store
async addUser({commit}) {
try {
const {data} = await apiService.addUser()
commit('SET_USER', data)
commit('SET_NOTIFICATION', {type:'success', message: 'user successfuly created'})
} catch (error) {
commit('SET_NOTIFICATION', {type:'error', message:error})
}
}
SET_USER(state, user) {
state.users.push(user)
}
//my component:
async addUser() {
this.isLoading = true
await this.$store.dispatch('updatePatient', this.form)
this.isLoading = false
}
is it legit ?
sometimes i think i would need more logic inside my component depending on the succes or rejected api request. Should i put all the logic in my actions ? like i do at the moment ?
Maybe should I add a status state for each actions, for example :
state {
users: []
postUserSuccess: null
postUserError: false
updateUserSuccess: null
updateUserError: false
// ...
}
and do what i want in the component with a computed property mapped to the store ?
What do you think ?
I don't know if it's a best practice but I let the components the exception handling. That method has its pros (you don't have to pollute the state with error management) and cons (you have to repeat the error management code for every action call).
All service calls will be made in actions
The state will only be set in mutations.
All service calls will return a promise with a resolve(data to load in the state) and a reject(message errors to present).
There will be an interceptor to reject the response in case there's a custom error (here you can put if the response has an error prop reject the response and send as an error the error prop, now you don't have to deconstruct the response in the action).
I'm going to give you a simplified example (I use axios, you can learn how to do it with the library that you use).
Actions in Vuex are asynchronous. So you don't need to try/catch them.
ApiService - Add User
const addUser = () => {
return new Promise((resolve, reject) => {
axios
.post(url, user)
.then(response => resolve(response.data))
.catch(error => reject(error));
});
};
store
async addUser({commit}) {
const data = await apiService.addUser();
commit('SET_USER', data);
return data;
}
if the promise in apiService.addUser is resolved the commit is going to be made if is rejected axios will return the promise and you can catch the error in the component that calls the action.
Component
async addUser() {
this.isLoading = true;
try {
await this.$store.dispatch('updatePatient', this.form);
} catch (error) {
// here goes the code to display the error or do x if there is an error,
// sometimes I store an errors array in the data of the component other times I do x logic
}
this.isLoading = false;
}
State
Your state will be cleaner now that you don't need to store those errors there.
state {
users: []
}

reactjs try catch in render does not catch children errors

I'm trying to add error catching to the render function of a component. This works fine when I throw an error in the actual render function, but if there are errors in the children of the component, the try does not catch the errors (or they are intercepted by the child component error handlers, I'm not sure?)
Is there anyway to force the errors to the parent.
const SimpleComponent = React.createClass({
render: function(){
try{
throw 'new error'
return <div>{this.props.children}</div>
}catch(e){
console.log('error', e);
}
}
})
The above works
const SimpleComponent = React.createClass({
render: function(){
try{
return <div>{this.props.children}</div>
}catch(e){
console.log('error', e);
}
}
})
const ChildComponent = React.createClass({
render: function(){
throw 'child error'
}
})
<SimpleComponent>
<ChildComponent />
</SimpleComponent>
This above does not catch
Use componentDidCatch() method from react 16.
Check this for more info
You can leverage React's BatchingStrategy API to easily wrap a try/catch around all of your React code. The benefit of this over window.onerror is that you get a nice stack trace in all browsers. Even modern browsers like Microsoft Edge and Safari don't provide stack traces to window.onerror.
Note that this solution won't always prevent React from getting into a bad state. However, this solution will at least allow you to handle the error, for example displaying an error banner/modal, or sending stack trace error logs to your service.
Here's what it looks like with React 15.4:
import ReactUpdates from "react-dom/lib/ReactUpdates";
import ReactDefaultBatchingStrategy from "react-dom/lib/ReactDefaultBatchingStrategy";
let isHandlingError = false;
const ReactTryCatchBatchingStrategy = {
// this is part of the BatchingStrategy API. simply pass along
// what the default batching strategy would do.
get isBatchingUpdates () { return ReactDefaultBatchingStrategy.isBatchingUpdates; },
batchedUpdates (...args) {
try {
ReactDefaultBatchingStrategy.batchedUpdates(...args);
} catch (e) {
if (isHandlingError) {
// our error handling code threw an error. just throw now
throw e;
}
isHandlingError = true;
try {
// replace this with whatever error handling logic you like.
// e.g. dispatch redux action notifying the app that an error occurred:
// `store.dispatch({type: "EXCEPTION", payload: e});`
console.error(e);
} finally {
isHandlingError = false;
}
}
},
};
ReactUpdates.injection.injectBatchingStrategy(ReactTryCatchBatchingStrategy);
Full writeup here: https://engineering.classdojo.com/blog/2016/12/10/catching-react-errors/
I'd recommend the (currently) unstable lifecycle event unstable_handleError in your parent component.
public unstable_handleError(err: any) {
this.setState({
error: err
});
}
The error hook will likely become official API in future releases.
See this issue for details
https://github.com/facebook/react/issues/2461