Catch React Native fetch error without halting the app with red screen - react-native

I am making a React Native function that pulls the HTML of a webpage. It works fine if the URL exists and I receive a 200 status code. However, when I put a wrong url in there (something that would receive a 404 error), it displays a red screen that says "Network request failed." I'd like to catch the error without the whole app halting and display an alert to the user. How can I go about doing that?
fetchEvents() {
fetch('http://www.wrongurl.com', {
method: 'GET',
redirect: 'follow'
})
.then(function(response) {
if (response.status == 200) {
let responseText = JSON.stringify(response.text());
console.log(responseText);
}
else throw new Error('HTTP response status not code 200 as expected.');
})
.catch(function(error) {
console.error(error);
return error;
});
}

This is how I solved this, making graceful errors that don't crash the app using promises:
In my API service class:
fetchEvents() {
let thisCurrentAPIService = this;
return new Promise(
function (resolve, reject) {
fetch('http://www.wrongurl.com');
.then(
function(response) {
if (response.ok) {
let responseText = JSON.stringify(response.text());
console.log(responseText);
}
else {
reject(new Error(`Unable to retrieve events.\nInvalid response received - (${response.status}).`));
}
}
)
.catch(
function(error) {
reject(new Error(`Unable to retrieve events.\n${error.message}`));
}
);
}
);
}
Then I call it from my React Component. If I receive an error, I create the alert there.
this.state.apiService.fetchEvents()
.then(
function (value) {
console.log('Contents: ' + value);
},
function (reason) {
Alert.alert(`${reason.message}`);
});

Hilarious, three years almost and no proper answer still.
However, console.error(error) is what actually causing the app to throw a red screen.

You can use Alert component from react-native.
fetchEvents() {
fetch('http://www.wrongurl.com', {
method: 'GET',
redirect: 'follow'
})
.then(function(response) {
if (response.status == 200) {
let responseText = JSON.stringify(response.text());
console.log(responseText);
}
else throw new Error('HTTP response status not code 200 as expected.');
})
.catch(function(error) {
Alert.alert(error); // Using this line
});
}
But I prefer using toast like on Android than alert.

console.warn('This is my error');
If this is simply for dev it might help. It explicitly uses the little warning toast to provide whatever feedback you need. Note: this is definitely not for production use.

Add following in app index.js file
console.reportErrorsAsExceptions = false;

Related

axios interceptor blocking api calls in redux saga

I have a react native project in which I'm calling some API's using redux-saga mechanism. Now when I added response interceptor for axios my saga api's are not working anymore. Does any knows how I can fix this?
here is the code for my axios instance class and response interceptor
const getLoggedInUser = async () => {
const savedUser = JSON.parse(
await getDataFromAsyncStorage(APP_CONSTANTS.SAVED_USER)
)
if (savedUser?.user_id != null) {
return savedUser
}
return null
}
const baseapi = axios.create({
baseURL: APP_CONSTANTS.BASE_URL,
headers: {},
})
baseapi.interceptors.request.use(
async (config) => {
const token = await getLoggedInUser()
const userId = token?.user_id
const authToken = token?.token
if (token) {
baseapi.defaults.headers.common['userId'] = token
baseapi.defaults.headers.common['token'] = authToken
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// Response interceptor for API calls
baseapi.interceptors.response.use(
(response) => {
return response
},
async function (error) {
const originalRequest = error.config
if (error.response.status === 403 /* && !originalRequest._retry */) {
return baseapi(originalRequest)
}
return Promise.reject(error)
}
)
This is my saga class code and it fails directly when I add a response interceptor
function* getTopicList(action) {
try {
yield put({type: ACTION_TYPES.START_TOPIC_LIST})
const {payload} = action
const res = yield call(getAllTopicsOfBatch, payload)
if (res?.status == APP_CONSTANTS.SUCCESS_STATUS) {
yield put({
type: ACTION_TYPES.SET_TOPIC_LIST,
payload: {data: res?.data?.topics},
})
} else {
alert('OOPS Something went wrong! Please try again')
yield put({
type: ACTION_TYPES.ERROR_TOPIC_LIST,
payload: 'Something Went Wrong Please Try Again',
})
}
} catch (error) {
console.log('RESPONES error', error)
alert('OOPS Something went wrong! Please try again')
yield put({
type: ACTION_TYPES.ERROR_TOPIC_LIST,
payload: 'Something Went Wrong Please Try Again',
})
}
}
The code looks mostly fine, the only two things I found that are likely causing problems are:
In the request interceptors you are likely wrongly passing the whole token as userId instead of userId
baseapi.defaults.headers.common['userId'] = token // 'token' should be 'userId'
In the response interceptors error handler, you are not guaranteed to have 'response' property on error.
if (error.response.status === 403) // use error?.response
If neither of these things will fix your problem my guess is you have a problem in your endpoint and so you should examine the response errors you get to guide you.

Nuxt handle fetch errors from Prismic API

I'm building a blog with Nuxt to and Prismic as CMS.
my nuxt.config.js looks like this:
mode: 'universal',
modules: ['#nuxtjs/prismic'],
target: 'static',
generate: {
fallback: '404.html',
},
Project is deployed on Netlify with build command "npm run generate"
In pages directory I have dynamic links ( _uid.vue ) where I use the new fetch to get the post according to route.
async fetch() {
const post = await this.$prismic.api.getByUID('blog-post', this.$route.params.uid)
this.post = post
},
This all works! However I want to handle fetch errors and display correspond error page. For example when the post we try to fetch does not exist or now is deleted. I tried as they show from the link I provide above about fetch, but I get error that post is undefined.
async fetch() {
const post = await await this.$prismic.api.getByUID('blog-post', this.$route.params.uid)
if (post.id === this.$route.params.id) {
this.post = post
} else {
// set status code on server and
if (process.server) {
this.$nuxt.context.res.statusCode = 404
}
// use throw new Error()
throw new Error('Post not found')
}
}
My project on GitHub
Also I'm not sure using the fetch hook inside a page is considered a best practice, I think you should prefer asyncData with the following pattern (or async/await one):
export default {
asyncData({ params, error }) {
return axios
.get(`https://my-api/posts/${params.id}`)
.then(res => {
return { title: res.data.title }
})
.catch(e => {
error({ statusCode: 404, message: 'Post not found' })
})
}
}
From Nuxt documentation~
Could you not just catch any exceptions like this:
try {
const post = await this.$prismic.api.getByUID('blog-post', this.$route.params.uid);
if (post.id === this.$route.params.id) {
this.post = post;
}
} catch ((error) => {
// set status code on server and
if (process.server) {
this.$nuxt.context.res.statusCode = 404;
}
// use throw new Error()
throw new Error('Post not found');
});
Of course you would have to actually check the kind of exception occurred.

handle network request failed in react native

I'm facing an issue while using react native fetch api. many times request got failure . I have a high speed connection. but many times it got failed.
that issue is happening In android,ios both.
const shoppingApi = 'myserverlink';
async function Sendshoppinapi(data) {
try {
let response = await fetch(shoppingApi, {
method: 'POST',
headers: {
'Accept': 'application/json',
'content-type':'multipart/form-data'
},
body: data
});
let responseJson = await response.json();
return responseJson;
}
catch (error) {
Alert.alert(error.toString())
}
}
export {Sendshoppinapi};
data that I sending server as post request
add_to_wishlist = (item,index) => {
{
let data = new FormData();
data.append('methodName', 'add_to_wishlist');
data.append('user_id', global.userid)
data.append('item_id', this.props.navigation.state.params.itemid.toString())
Sendshoppinapi(data).then((responseJson)=>{
console.warn(responseJson);
if(responseJson.responseCode == '200'){
this.setState({fav:false})
Alert.alert('SHOPPING','Item added to wishlist successfully.',[{text: 'OK',},],{ cancelable: false })
}
else{
this.setState({fav:false})
Alert.alert('SHOPPING','Item already .',[{text: 'OK',},],{ cancelable: false })
}
})}
}
Error that when request got failed
I've quoted an answer I used for another post - however I have added await.
You can check the status of the call, to determine perhaps why the network call failed. Try using fetch's ok to check whether the response was valid, for example:
.then(function(response) {
if (!response.ok) {
//throw error
} else {
//valid response
}
})
Using await:
let response = await fetch(url)
if (response.ok) return await response.json()
You can also access the response's status like:
response.status;
or also, statusText such as:
response.statusText;
checkout the below:
https://developer.mozilla.org/en-US/docs/Web/API/Response/statusText
https://developer.mozilla.org/en-US/docs/Web/API/Response/status
https://www.tjvantoll.com/2015/09/13/fetch-and-errors/
Use then() function with promises. (Requested code snippet)
fetch(shoppingApi, {
method: 'POST',
headers: {
'Accept': 'application/json',
'content-type':'multipart/form-data'
},
body: data
})
.then((resp) => {
return resp.json()
})
.then((resp) => {
//resp contains your json data
});
You also can make your function returns a Promise, and use it with then():
function sendShoppingApi(data) {
return new Promise((resolve, reject) => {
fetch(shoppingApi, {
method: 'POST',
headers: {
'Accept': 'application/json',
'content-type':'multipart/form-data'
},
body: data
})
.then((resp) => {
return resp.json();
})
.then((resp) => {
resolve(resp);
/*
you should also check if data is valid, if something went wrong
you can reject the promise:
if(!dataOK)
reject("error message");
*/
});
});
}
So now you can do something like this:
sendShoppingApi(data)
.then((resp) => {
//do stuff with your data
})
.catch((err) => {
//handle error
});
UPDATE
could be a duplicate of this: React Native fetch() Network Request Failed
For the case when you are running the app on the android device, the API is on a computer and both of them are on the same network I have added some possible things to check. I haven't detailed specific solutions since there are many answers on each topic.
Do a quick check with ngrok https://ngrok.com/ on the free plan to see if that works. If yes:
Make sure the API is accessible by trying to access it on the device browser (most important is to check if you allow the port at inbound rules, firewall).
If you are using HTTPS, you might get an error if your react native env is not properly configured to accept not trusted certificates, assuming you are using a non trusted one. Do a check without HTTPS, only with HTTP, to see if it's the case. https://github.com/facebook/react-native/issues/20488

.catch() in react-native's fetch

This is my code to handle signUp
handleSignUp(){
console.log('Clicked')
fetch('http://laptopIp:9999/signUp',{
method:'POST',
headers:{
Accept:'application/json',
'Content-Type':'application/json',
},
body:JSON.stringify(this.state),
})
.then((res)=>{
console.log("here")
console.log(res)
this.setState({error:false})
}).catch((e)=>{
console.log(e)
console.log("in error")
})
this is express server handling the requset
router.post('/signUp',function(reqs,resp){
MongoClient.connect(url,{ useNewUrlParser: true },function(err,database){
if(err){
console.log(err)
}else{
var dataBases = database.db("thaparApp")
dataBases.collection('userLogin').find({'email':reqs.body.email}).toArray(function(err,result){
if(err){
console.log(err)
}else if(result[0]){
resp.status(403).send({"error":"email is taken"})
}else{
if(validateEmail(reqs.body.email)){
dataBases.collection('userLogin').insertOne(reqs.body)
resp.send()
}else{
resp.status(403).send({"error":"Email is not correct"})
}
}
})
}
})
})
}
What i am doing is sending same username from react-native and express is sending me error 403 but React native is not handling that error in .catch() instead keeping that in .then()
as we can see in image That status code is 403 which is error and .catch() have console.log("in error") which is not getting print.
The call is coming back from the server, although it's status is not what you expected, it's not an error to catch.
Try using fetch's ok to check whether the response was valid, for example:
.then(function(response) {
if (!response.ok) {
//throw error
} else {
//valid response
}
})
You can also access the response's status like:
response.status;
or also, statusText such as:
response.statusText;
checkout the below:
https://developer.mozilla.org/en-US/docs/Web/API/Response/statusText
https://developer.mozilla.org/en-US/docs/Web/API/Response/status
https://www.tjvantoll.com/2015/09/13/fetch-and-errors/

react native AsyncStorage not working?

for setting the item i am using this first code.
console.log(ACCESS_TOKEN);
console.log(typeof(ACCESS_TOKEN));
async function setToken() {
try {
await AsyncStorage.setItem('access_token', ACCESS_TOKEN);
} catch (error) {
console.log("token not set")
}
}
setToken();
for getting the item , i am using this code
componentWillMount(){
async function getToken() {
try {
const value = await AsyncStorage.getItem('access_token');
if (value !== null){
console.log(value);
this.setState({ access_token: value });
}
} catch (error) {
console.log( "Error retrieving data")
}
}
getToken();
the result i am getting in first code is
1a61b72b-ee83-43de-9cf9-3fa270ce694d
string
but getting nothing at console at getting code . why ?
You can try this one:
set item using this
AsyncStorage.setItem('access_token', ACCESS_TOKEN);
get item using this
try {
AsyncStorage.getItem('access_token')
.then((value) => {
if (value) {
// you will get your access_token here
}
}).done();
} catch (error) {
console.log(error); // you will get error here
}
To set the item
AsyncStorage.setItem('access_token', ACCESS_TOKEN)
.then(() => {
//your success code
})
.catch((error) => {
//your failure code
})
To get the item
AsyncStorage.getItem('access_token')
.then(access_token=> {
//you will be getting the access token here
})
.catch(err => {
//handle the error
})
Maybe this is what you intended:
async function getToken() {
try {
const value = await AsyncStorage.getItem('access_token');
if (value !== null){
console.log(value);
this.setState({ access_token: value });
}
} catch (error) {
console.log( "Error retrieving data")
}
}
componentWillMount(){
getToken();
}
If instead your code is getting stuck while working with AsyncStorage, there's a well known bug reproducible only in development: https://github.com/facebook/react-native/issues/12830
They only way to bypass it, afaik, it is just to restart the debugger when you get stuck
Remember that setting state during componentWillMount is not the correct way to do it. You should use the constuctor for that
My answer might not directly apply to your case, But I had the same problem as you did with AsyncStorage and It was getting stuck, so this might help other people.
As stated in this answer:
AsyncStorage only takes string as a value and key.
But I was accidentally giving it null (which as you know is considered an object) so it got stuck. So check if you are passing a string value as both the first and second argument to setItem.