react-native - call another function when fetch is over - javascript

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

Related

Is there a better way to store data from an API in state?

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.

$emit inside async method - Vue2

Here's my child's component async method:
async created () {
this.$parent.$emit('loader', true)
await this.fetchData()
this.$parent.$emit('loader', false)
}
fetchData does an axios get call, to fetch data from API. However in a vue-devtools (events tab) i can only see the events, after i change the code and it hot reloads. Also i've set up console.log() in a parent component:
mounted() {
this.$on('loader', (value) => {
console.log(value)
})
}
And i can see only false in a console. My purpose is to emit loader set to true (so i can show the loader), then set it to false, when data is fetched.
My fetchData method:
import http from '#/http'
fetchData() {
return http.getOffers().then((resp) => {
this.offersData = resp.data
})
}
Contents of http.js:
import axios from 'axios'
import config from '#/config'
const HTTP = axios.create({
baseURL: config.API_URL
})
export default {
/* calculator */
getOffers() {
return HTTP.get('/url')
}
}
If i directly use return axios.get() in async created(), then it works. Problem is in this imported http instance.
Final solution
One of the problems was using different lifecycles, thanks to Evan for mentioning this.
Another problem was with async / await usage, changes to a fetchData() method:
import http from '#/http'
async fetchData() {
await http.getOffers().then((resp) => {
this.offersData = resp.data
})
}
I had to make this method async and use await on axios request, since await is thenable, it does work. Also i've spotted an issue in https.js:
export default {
/* calculator */
getOffers() {
return HTTP.get('/url')
}
}
It returns HTTP.get(), not a promise itself, i could have used then here, and it would work, but, for flexibility purposes i didn't do that.
But, still, i don't get why it didn't work:
fetchData() {
return http.getOffers().then((resp) => {
this.offersData = resp.data
})
}
Isn't it already returning a promise, since it's chained with then... So confusing.
Retested again, seems like return is working, lol.
The issue here is that created on the child component is getting called before mounted on the parent component, so you're beginning to listen after you've already started your Axios call.
The created lifecycle event method does not do anything with a returned promise, so your method returns right after you begin the Axios call and the rest of the vue component lifecycle continues.
You should be able to change your parent observation to the created event to make this work:
created() {
this.$on('loader', (value) => {
console.log(value)
})
}
If for some reason you need to do something that can't be accessed in created, such as accessing $el, I'd suggest moving both to the mounted lifecycle hook.
I'd simply suggest restructuring your method, as there isn't really a need to make an async method since axios itself is asnychronus.
If you already have the fetchData method defined, and the goal is to toggle the loader state when a call is being made, something like this should do.
fetchData () {
this.$parent.$emit("loader", true)
axios.get(url)
.then(resp => {
this.data = resp
this.$parent.$emit("loader", false)
})
}
Of course these then statements could be combined into one, but it's the same idea.
Edit: (using the parent emit function)
fetchData () {
this.loader = true
axios.get(url)
.then(resp => this.data = resp)
.then(() => this.loader = false)
}
If what you are trying to achieve is to tell the direct parent that it's no longer loading, you would have to emit to the same instance like so
async created () {
this.$emit('loader', true)
await this.fetchData()
this.$emit('loader', false)
}
By removing the$parent, you will emit from the current component.
--Root
--My-page.vue
-Some-child.vue
Now you will emit from some-child.vue to my-page.vue. I have not tried, but theoretically what you are doing by emiting via parent: (this.$parent.$emit('loader', false)) You are emitting from my-page.vue to root.
So If you have a $on or #loader on the component like so: <Some-child #loader="doSomething"/>, This will never run due to you emitting from the parent.

State Updating in catch

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.

Redux action not getting called the second time

I have two redux actions which call as follows.
export function action1(params) {
//This line is always called.
return (dispatch) => {
//This line is not called the second time.
return MyApi.call1(params)
.then(response => {
// some logic
return dispatch(someFunction1());
})
.catch(error => {
throw(error);
});
};
}
export function action2(params) {
return (dispatch) => {
return MyApi.call2(params)
.then(response => {
// call the first API again
action1();
return dispatch(someFunction2());
})
.catch(error => {
throw(error);
});
};
}
When the view is first loaded, action1 is called within the constructor of the view. Upon performing an action and triggering action2 in the same view, action1 needs to be called on action2's success to get the updated list from the server. Unfortunately, code breaks without any error when action1 is called the second time.
What am I missing here?
You have not dispatched the action1.
dispatch( action1( params ) )
Invoking action1() without dispatch just returns a function. In order to get dispatch in returned function, you should dispatch that function. Then it will be caught by redux-thunk middleware. The middleware will pass dispatch and invoke function.

Infinite loop when dispatching in componentWillMount

I'm working in a React + Redux + redux-thunk codebase and I'm seeing some odd behavior. If I attempt to execute TWO actions in componentWillMount, the second action will infinitely loop.
Here's the componentWillMount:
componentWillMount() {
const { actions } = this.props;
// Action #1 (synchronous)
actions.openLoader();
// Action #2 (promise-based fetch)
actions.getListingsPurchased().then(() => {
actions.closeLoader();
})
.catch(error => {
console.error(error);
});
}
The first action, openLoader() is a simple state update. The second action does a fetch to the server. Action file here:
export function openLoader() {
return {
type: TYPES.SET_LOADER_OPEN
};
}
export function getListingsPurchased() {
return dispatch => {
return fetch'URL GOES HERE', { 'credentials': 'include' })
.then(response => {
return response.json();
})
.then(response => {
return dispatch({ type: TYPES.SET_LISTINGS, data: response.data });
});
};
}
If I was to remove the first action openLoader() from componentWillMount the infinite loop does not happen. Otherwise the fetch call will keep repeating endlessly.
Any help would be appreciated, I seem to have hit a wall.
I believe the best place for breaking infinite loop is in Redux reducer. Reducer is the place where you have to decide if you going to update the state of your app -> will trigger re-render of your components -> will trigger fetch action.
So try to put in place some reducer condition where you can recognize that state was already fetched before and you not going to update the state.

Categories