Fetch is done after statements in .then - javascript

I have component which is fetching data from server, and then passing result to another component.
getData(args) {
return fetch(`${process.env.REACT_APP_API}/offers?title=${args}`)
.then(response => response.json())
.then((services) => { this.setState({ services }); });
}
In child component I want to pass url in callback function and then do something with result (in this case console.log services), but console.log is firing before fetching data is over.
handleSubmit(event) {
event.preventDefault();
if (this.state.validPhrase === false) return;
this.props.updateSearchPhrase(this.state.searchPhrase);
this.props.onSubmit(this.state.searchPhrase)
.then(console.log(this.props.services));
}
How can I fix this?

then(console.log(this.props.services));
You're immediately calling the console here. You need to enclose it in a function:
then(() => console.log(this.props.services));

Related

How to identify when fetch is completed in vue.js

So I am making an api call in my created() hook, but there are a few things in my app I want to trigger AFTER the api call is finished, but I'm not sure how to do that. On average my API call takes about 5 seconds to return a huge chunk of json (terrible i know). In the example below, the logging statement prints well before the api call has finished.
snippet from component:
<script>
created() {
this.myEndpoint = 'testserver.com'
fetch(this.myEndpoint)
.then(response => response.json())
.then(body => {
for(let i=0; i<body.length; i++){
this.job_execs.push({
'version': body[i].version,
'platform': body[i].platform.name,
})
}
})
.then(console.log('print AFTER the api call is completed'))
.catch( err => {
console.log('Error Fetching:', this.myEndpoint, err);
return { 'failure': this.myEndpoint, 'reason': err };
})
},
};
</script>
I have tried moving the console.log statement to the mounted() hook, but this did not work either.
I believe I could achieve what I want using:
$(window).on('load', function(){
console.log('this logs after the entire page loads')
});
But I'm sure there is a more elegant vue.js solution.
How do I identify in vue.js when the api call in my example has completed
Your code is fine in concept.
The problem is
.then(console.log('print AFTER the api call is completed'))
Even though promise.then calls register async handlers, the calls themselves are evaluated synchronously, they are just supposed to take async callback functions as arguments. When you call
.then(console.log('print AFTER the api call is completed'))
console.log('print AFTER the api call is completed') is evaluated synchronously (logging out your message) and its return value (undefined) is then passed to .then as the callback.
Pass in a function here instead and you should see your log come at the appropriate time:
.then(() => console.log('print AFTER the api call is completed'))
You need to pass a function to the then statement. What you have will execute the console.log and pass its result to the then statement (which is undefined/void).
created() {
this.myEndpoint = 'testserver.com'
fetch(this.myEndpoint)
.then(response => response.json())
.then(body => {
for (let i = 0; i < body.length; i++) {
this.job_execs.push({
'version': body[i].version,
'platform': body[i].platform.name
})
}
})
.then(() => console.log('print AFTER the api call is completed'))
.catch(err => {
console.log('Error Fetching:', this.myEndpoint, err);
return {
'failure': this.myEndpoint,
'reason': err
};
})
}

Chain React setState callbacks

I need to load three different json files in an ordered sequence and with a fetch (the reason is i'm using nextjs export and i need those files to be read dynamically, so I fetch them when needed and their content can change even after the export)
The first file contains data that is used to create the url for the second file and so on, so each fetch needs an actually updated state to be fetched,
ATM the solution i'm using, since the second and third files are dependent from the first and second respectively, is fetching the first file and setting some state with setState, then in the setState callback fetch the second file and set some other state and so on:
fetch(baseUrl).then(
response => response.json()
).then(
res => {
this.setState({
...
}, () => {
fetch(anotherUrl+dataFromUpdatedState).then(
response => response.json()
).then(
res => {
this.setState({
...
}, () => {
fetch(anotherUrl+dataFromUpdatedState).then(
response => response.json()
).then(
res => {
this.setState({
})
}
)
})
}
).catch(
error => {
//error handling
}
)
})
}
).catch(
error => {
this.setState({ //an error occured, fallback to default
market: defaultMarket,
language: defaultLanguage,
questions: defaultQuestions
})
//this.setLanguage();
}
)
Now: I know that setState must be used carefully as it is async, but as far as I know the callback function is called after state is updated so from that point of view the state should update correctly. Is this solution anti-pattern, bad practice or should be avoided for some reason?
The code actually works, but i'm not sure if this is the way to do it.
You don't need to use the setState callback and read it from the state, since you can just read the data directly from the res object. This way you can make a flat promise chain.
Example
fetch(baseUrl)
.then(response => response.json())
.then(res => {
this.setState({
// ...
});
return fetch(anotherUrl + dataFromRes);
})
.then(response => response.json())
.then(res => {
this.setState({
// ...
});
return fetch(anotherUrl + dataFromRes);
})
.then(response => response.json())
.then(res => {
this.setState({
// ...
});
})
.catch(error => {
this.setState({
market: defaultMarket,
language: defaultLanguage,
questions: defaultQuestions
});
});

Logging Data after Subscribe in Angular Observables

I'm new to the Observables and working my way through the Angular documentation.
Here's how I subscribe to the service and get data.
this.dataService.getData().subscribe(data => this.localData = data)
Now, I'm struggling to log it within the component (console.log) for debug purposes.
I've tried:
this.dataService.getData().subscribe(data => this.localData = data, complete => console.log(this.localData));
You can call console.log inside of the "success" callback:
this.dataService.getData().subscribe(data => {
this.localData = data;
console.log(this.localData);
});
If you want to do it in the "complete" callback (assuming that the Observable does complete):
this.dataService.getData().subscribe(
data => { this.localData = data; },
error => {},
() => { console.log(this.localData); });
this.dataService.getData().subscribe(
(data) => {
this.localData = data,
console.log(this.localData)
}
, error => // the second one is error!),
, complete => //complete is the third one;
You need to console your data inside subscribe on success or in complete. You are printing inside when the subscription returns a error

Asynchronous ActionCreator in React Redux

I'm pretty new in React-Redux. Was working on an application. The thing is that I faced some issues with asynchronous execution of Redux actionCreator, may be.
Below is my component. Say, I want to call an actionCreator from componentDidMount() or from an onclick event listener.
class Dashboard extends PureComponent {
componentDidMount() {
this.props.getProductsAndPackages();
let something = [];
something = this.props.products;
}
....................................
}
Or , the function this.props.getProductsAndPackages(); can be an onClick event handler that does the same thing, context is the same. I'll ask my question after first explaining my code.
At the lower side of my Dashboard container:
Dashboard.propTypes = {
getProductsAndPackages: PropTypes.func.isRequired,
products: PropTypes.array.isRequired,
.......................
};
const mapStateToProps = (state) => {
return {
.....................
products: state.products.products,
...................
};
};
const mapDispatchToProps = (dispatch) => {
return {
getProductsAndPackages: () => dispatch(getProductsAndPackagesActionCreator()),
};
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Dashboard));
My actionCreator goes like:
export const getProductsAndPackagesActionCreator = () => {
return (dispatch) => {
dispatch(productsIsLoading(true));
let url = 'xyz';
if(!!localStorage.getItem('_token')) {
const local_token = localStorage.getItem('_token');
const fullToken = 'Bearer '.concat(local_token);
axios.get(url, {headers: {Authorization: fullToken}})
.then(response => {
dispatch(productsIsLoading(false));
if (response.data.statusCode === 200) {
dispatch(productsFetched(true));
dispatch(products(response.data.data));
} else {
dispatch(productsFetched(false));
dispatch(productsErrors(response.data.message));
}
})
.catch(error => {
});
} else {
axios.get(url)
.then(response => {
dispatch(productsIsLoading(false));
if (response.data.statusCode === 200) {
dispatch(productsFetched(true));
dispatch(products(response.data.data));
} else {
dispatch(productsFetched(false));
dispatch(productsErrors(response.data.message));
}
})
.catch(error => {
console.log(error);
dispatch(productsIsLoading(false));
dispatch(productsErrors(error.message));
});
}
};
};
Now, I want my getProductsAndPackagesActionCreator() to return a Promise or anything that would allow my something variable to get the actual data returned from the server. Right now, by the time I'm getting actual data, the line something=this.props.products has already been executed and I get back the initialValue that was set for products.
I know, whenever I'll receive the populated products, component will re-render, but that does not help my decision making.
I'm using redux-thunk, by the way.
What should I do now ? Sorry for such a long post.
Actually I wanted getProductsAndPackagesActionCreator() to return a promise, which was pretty straightforward, to be honest. I figured out that if you just return the axios.get() or axios.post(), it will return a promise. So, the modified code looked like below:
export const getProductsAndPackagesActionCreator = () => {
return (dispatch) => {
dispatch(productsIsLoading(true));
let url = 'xyz';
if(!!localStorage.getItem('_token')) {
return axios.get(url, {headers: {Authorization: fullToken}})
.then(response => {
............
............
})
.catch(error => {
});
} else {
return axios.get(url)
.then(response => {
...........
...........
})
.catch(error => {
console.log(error);
});
}
};
};
And then, I could do something like below in componentDidMount() or on any onClick event:
this.props.getProductsAndPackages().then(() => {
this.setState({
...this.state,
clicked_product: this.props.product_by_id
}, () => {
//do other stuffs
});
});
Feel free to let me know if there's any issue.
I think you are close to getting what you want. First of all, you should understand that redux actions and react actions like setState are asynchronous, so you have to apply your logic keeping this in mind. I'm going to explain what i think in some points:
You have called the action creator in the correct place componentDidMount, also you can call this action in any onClick if you want.
As soon as you dispatch the action you are changing your redux state setting loading true I suppose. So now you can access this property in your render function, so you can render a Loader until your api call finishes.
When your ajax function finishes, with an error or not, I suppose you are setting loading to false and updating your products data, so you can render now your loaded products in your dashboard.
Are you sure that you have to compare your empty products array with the received data? Maybe you can check in your render function if (!this.props.products.length) return null, when you load your page you will see a loader function and later your dashboard with the products.
If you really need to compare previous products with received products componentDidUpdate is your method. In this method, you can access your previous props and compare with actual props, be careful comparing arrays, remember [] === [] is false. Maybe you can compare the length, something like
componentDidUpdate(prevProps){
if(prevProps.products.length !=== this.props.products.lenth){
doSomething()
}
}
Just to say that componentDidUpdate is executed after render, so be careful with your code to no-execute extra renderings.
Hope it helps, if you dont understand anyting just tell me :)

ReactJS: Uncaught (in promise) this.setState is not a function

I am dealing with the following frustrating error:
Home.js:231 Uncaught (in promise) TypeError: _this9.setState is not a function. The error is coming from the last line of the following function:
checkIfRunning() {
return fetch('/api/following/iscurrentlyrunning', {
credentials: 'include',
})
.then(response => {
console.log(response.status);
if (response.status === 200) {
return response.json();
}
})
.then(response => {
let cState = this.state;
cState.running = response;
this.setState(cState);
});
}
I did bind the function in the component constructor and when I call it alone, it works fine. The issue arise when I try to invoke the function in a timer (setInterval). In componentWillMount, I call few functions:
componentWillMount() {
this.checkIfFirstTimeLogin()
.then(() => {
// user already exists
if (!this.state.firstLogin) {
this.Name();
this.getRole();
setInterval(() => this.checkIfRunning(), 10000);
}
})
.then(() => {
let cState = this.state;
cState.pageLoading = false;
this.setState(cState);
})
.catch(error => console.log(error));
}
I have the intuition that the promise chain breaks the binding for a reason I do not presently understand.
Thank you for any help,
Promises are a guaranteed future, which means the whole promise chain will fire once invoked and there's little you can do to stop it.
On a practical level, this means you need to check to be sure that your component instance is still mounted before trying to access setState off it, as the component may have unmounted before this promise chain completes.
.then(response => {
...code here...
// important! check that the instance is still mounted!
if (this.setState) {
this.setState(cState);
}
});
Also, you should never mutate local state directly as you are doing here:
// don't mutate state directly, use setState!
let cState = this.state;
cState.running = response;
You are mutating the state directly, it is not allowed, in the final example you are still doing it. It is better to use an Object.assign(…) to create new object like this :
let newState = Object.assign({}, ...this.state, running: response);
Then, only do your setState() call
this.setState(newState);
One of the fundamental principles of React is that changes to State are not done directly but with the setState function which will put the change to queue, and it will be done either alone or with batch update.
You can try change function checkIfRunning() {} to checkIfRunning = () => {} to pass this into function
Thanks all for the help, very appreciated.
I solved the problem with the following fix, although I am not sure why it works now:
checkIfRunning() {
return fetch('/api/following/iscurrentlyrunning', {
credentials: 'include',
})
.then(response => {
console.log(response.status);
if (response.status === 200) {
return response.json();
}
})
.then(response => {
let cState = this.state;
cState.running = response;
this.setState({cState});
});
}
Notice how this.setState(cState) became this.setState({cState}).
Thanks all for your time, it led to interesting research on my part.

Categories

Resources