In compoenntDidMount lifecycle, I am fetching an API, getting data and catching the potential error. I can get the data properly. However, In catching the error stage, I would like to update my state as well but so weird, I cannot.
In state I have an isError boolean. It is false by default. When I change the api url in fetch, I can see console.log message, but my isError is still false.
componentDidMount() {
fetch(
"url"
)
.then(response => response.json())
.then(data => {
this.setState({
data1: data.response.venues,
});
})
.catch(error => {
this.setState({isError: true})
console.log("bla bla", error)
});
}
Nicholas Tower
is right i think.
First are you using ES6 or redux ?
Secondly try to pass your api call into an async function.
Catch the call with await to avoid setState execution as nicholas said.
If you use redux call your api with an action is a better practice.
A little example here : https://redux.js.org/advanced/asyncactions
maybe like that :
{
try {
const ret = await my_action(*);
}
catch (error) {
this.setState({
isError: true,
errorInState: error,
});
}
}
Sometimes the speed of execution surpasses some slower actions and creates creates incredible bugs.
Related
I'm developing the interface of a music web app , so i fetched data from an API and stored in state, all executed by one function , to be displayed on the interphase .The code is below :
/* function fetching the data*/
function getUsChart() {
const options = {
method: 'GET',
headers: {
'X-RapidAPI-Key': '036795ec2amsh8c2b98ef8a502acp146724jsn6f3538b26522',
'X-RapidAPI-Host': 'shazam-core.p.rapidapi.com'
}
};
fetch('https://shazam-core.p.rapidapi.com/v1/charts/genre-country?country_code=US&genre_code=HIP_HOP_RAP', options)
.then(response => response.json())
.then(response => setUsHopChart(response))
.catch(err => console.error(err));
/*I stored it in state here*/
setChartImg(usHopChart[1]?.images?.coverart)
}
/*I displayed it here*/
<img src={chartImg} className='chart-img rounded-3xl' alt='chart-image'/>
The issue:
After the function is executed , the data is fetched but not stored it in the state immediately until it's executed the second time. Hence causing this :
What can i do about this please?
i think you need to move the setChartImg inside
fetch('https://shazam-core.p.rapidapi.com/v1/charts/genre-country?country_code=US&genre_code=HIP_HOP_RAP', options)
.then(response => response.json())
.then(response => {
setUsHopChart(response)
setChartImg(response[1]?.images?.coverart)
})
.catch(err => console.error(err));
/*I stored it in state here*/
I think the problem is jsx is rendered before the fetch process is done. So, it is the best approach to create a boolean loading state and initialize it with true, when it's value is true create a spinner or smth and make it false when promise returns the value.
For quick solution maybe you can do something like that:
{chartImg && <img src={chartImg} className='chart-img rounded-3xl' alt='chart-image'/>}
So what it does is when chartImg is defined (when you give it a value after promise resolves) it will render the jsx element, which was your problem.
I think you want to fetch data faster and store it in the state. There is a way to that. I will give you an example
const commentsPromise = fetch('/get-comments');
const Comments = () => {
useEffect(() => {
const dataFetch = async () => {
// just await the variable here
const data = await (await commentsPromise).json();
setState(data);
};
dataFetch();
}, [url]);
}
In this example our fetch call basically “escapes” all React lifecycle and will be fired as soon as javascript is loaded on the page, before any of useEffect anywere are called.
Even before the very first request in the roop App component will be called. It will be fired, javascript will move on to other things to process, and the data will just sit there quietly until someone actually resolves it.
I have
useEffect(() => {
setLoading(true);
axios
.get(url, {params})
.then(data => {
setData(data || []);
setLoading(false);
})
.catch(() => {
showToast('Load data failed!', 'error');
setLoading(false);
});
}, [params]);
It gives me
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
Ok, the question IS NOT HOW TO SOLVE IT. When I use setLoading(false) after axios promise it works fine but inside of promise (e.g. above) it always gives me warning. Actually I want to know WHY IT HAPPENS SO? Is there anybody who may explain me in a nutshell a flow of code above (the process how code above works with warning) and maybe give some best practices on using hooks.
you need clean up function.
this means you should call function end of useEffect function.
when dependencie is changes (params as your example ) calls that function.
so we would be able controll when component mounts/unmounts
useEffect(() => {
let cancelled = false;
setLoading(false);
async function fetchData() {
try {
const response = await axios.get(url, { params });
if (!cancelled) {
setData(response.data);
setLoading(false);
}
} catch (e) {
if (!cancelled) {
showToast(e.message, "error");
setLoading(false);
}
}
}
fetchData();
// clean up here
return () => {
cancelled = true;
};
}, [params]);
WHY IT HAPPENS SO?
Imagine your request is goes slow, and the component has already unmounted when the async request finishes. this time throws this warning
I want to persist the fetched API data (Array) when reopening a React Native app without internet connection.
Fetching and storing the data in state and AsyncStorage works just fine using Axios.
When opening the app without internet connection, there is an endless load time, I cannot figure out how to trigger the catch promise and use my stored data as the new data for the state.
This is my code:
componentWillMount(){
axios.get('http://example.com/api_get_request')
.then((response) => {
console.log('connection ok!');
AsyncStorage.setItem('landen', JSON.stringify(response.data));
this.setState({
landen: response.data,
loading: false,
});
//DATA SET TO STATE + ASYNCSTORAGE
})
.catch(error => {
console.log(error.response);
AsyncStorage.getItem('landen').then((value) => {
this.setState({
landen: JSON.parse(value),
loading: false,
});
//NO CONNECTION -> ASYNC DATA
});
});
}
You can use NetInfo to check internet connection and as per connection execute your code like below.
componentWillMount() {
NetInfo.isConnected.fetch().then(isConnected => {
if (isConnected) {
axios.get('http://example.com/api_get_request')
.then((response) => {
AsyncStorage.setItem('landen', JSON.stringify(response.data));
this.setState({
landen: response.data,
loading: false,
});
})
} else {
AsyncStorage.getItem('landen').then((value) => {
this.setState({
landen: JSON.parse(value),
loading: false,
});
});
}
});
}
Please note, above code is not tested and you may have to fix few things here and there but you will get the concept. Also, you may have to add catch block to catch unexpected errors.
For manage some little info is a good idea use AsyncStorage. But if you need manage all your app states, it's a good practice passing your code to Redux and use libraries like:
https://github.com/rt2zz/redux-persist
https://github.com/redux-offline/redux-offline
I'm new to React-Native
I have fetch, through which I get some data. What I want to do is to call another function or update the state, after the request is over and data is ready. Here is my code.
getProducts()
{
return fetch(prodUrl, {method: "GET"})
.then((response) => response.json())
.then((responseData) => {
this.setState({brandList: responseData.products});
console.log("Brands State -> : ",this.state.brandList)
})
.done();
}
I call this getProducts() function in componentWillMount() and trying to use fetched data in render().
After I set the state, I can't see the change when I try to console.log(), most probably because fetch() is async. How can I stop execution of render() function before fetch() is over? Or can you recommend any other request type rather then fetch() which is sync.
It's not because fetch is async, you already have your responseData at that point. It is because setState doesn't change state immediately, so you're console.log is being called before state is being changed. setState has an optional callback as it's second parameter that will be called once set is done being updated, so you can change it like this to see the effect correctly:
getProducts()
{
return fetch(prodUrl, {method: "GET"})
.then((response) => response.json())
.then((responseData) => {
this.setState(
{brandList: responseData.products},
() => console.log("Brands State -> : ",this.state.brandList)
);
});
}
You do not want to "stop" the render() function from being executed. You can, however, apply a check in render if the data is available and render a spinner or something else while it is not.
Very rough sketch of how this could look like:
render() {
let component = this.state.brandList ? <ComponentWithData/> : <Spinner/>;
return component;
}
I am using fetch to make some API calls in react-native, sometimes randomly the fetch does not fire requests to server and my then or except blocks are not called. This happens randomly, I think there might be a race condition or something similar. After failing requests once like this, the requests to same API never get fired till I reload the app. Any ideas how to trace reason behind this. The code I used is below.
const host = liveBaseHost;
const url = `${host}${route}?observer_id=${user._id}`;
let options = Object.assign({
method: verb
}, params
? {
body: JSON.stringify(params)
}
: null);
options.headers = NimbusApi.headers(user)
return fetch(url, options).then(resp => {
let json = resp.json();
if (resp.ok) {
return json
}
return json.then(err => {
throw err
});
}).then(json => json);
Fetch might be throwing an error and you have not added the catch block. Try this:
return fetch(url, options)
.then((resp) => {
if (resp.ok) {
return resp.json()
.then((responseData) => {
return responseData;
});
}
return resp.json()
.then((error) => {
return Promise.reject(error);
});
})
.catch(err => {/* catch the error here */});
Remember that Promises usually have this format:
promise(params)
.then(resp => { /* This callback is called is promise is resolved */ },
cause => {/* This callback is called if primise is rejected */})
.catch(error => { /* This callback is called if an unmanaged error is thrown */ });
I'm using it in this way because I faced the same problem before.
Let me know if it helps to you.
Wrap your fetch in a try-catch:
let res;
try {
res = fetch();
} catch(err) {
console.error('err.message:', err.message);
}
If you are seeing "network failure error" it is either CORS or the really funny one, but it got me in the past, check that you are not in Airplane Mode.
I got stuck into this too, api call is neither going into then nor into catch. Make sure your phone and development code is connected to same Internet network, That worked out for me.