Calling a method after fetch has been executed - javascript

I am making a fetch request that returns an array of strings.
fetch(linkFetch)
.then(resp => resp.json())
.then(arr => {
that.setState({
images: arr
});
})
.then(this.test());
on the last line you can see I try to call the method test() which is just trying to access the state that was set in the fetch() request.
test(){
console.log(this.state.images[1]);
}
console logs 'undefined'
However, if I assign test() to a button or something so that I can manually call it works fine which leads me to believe that when I call .then(this.test()); in the fetch request it is actually being called before the state is set.
How can I make sure it gets called after the state has been set in the fetch request?

The argument you pass to then needs to be a function.
You are calling this.test immediately (i.e. before the asynchronous function has resolved) and passing its return value (undefined as there is no return statement).
You already have an example of how to do this correctly on the previous line of your code.
fetch(linkFetch)
.then(resp => resp.json())
.then(arr => {
that.setState({
images: arr
});
})
.then(() => {
this.test();
});

React provides a way to call a function after the setState has been executed. You can use the callback function as described in the react documentation
https://reactjs.org/docs/react-component.html#setstate
You can pass your desired function as a second argument to the setState call.
that.setState({
key:Val}, that.test);

Related

Trying to use fetch via js. It's returning as if its not actually calling any data. Where am I going wrong?

I'm having trouble finding any resources for this. All I need is help in the right direction to get something tangible to use in some HTML. So basically when I call console.log(keys) I don't get the json objects in my console like I'm used to. Project specifically requests it be done this way via fetch.
function getFromSWAPI() {
fetch("https://swapi.dev/api/people/1")
.then(function (response) {
return response.json()
})
.then(function(data){
updateInfo(data)
})
.catch(function(err) {
console.warn(err)
})
}
const updateInfo = responseJSON => {
console.log(responseJSON.data)
let keys = Object.keys(responseJSON.data)
console.log(keys)
}
So basically there is a minor bug in your code,
In this line
{...}
.then(function (response) {
return response.json()
})
{...}
you have already returned the json and thus you are accessing it in the next line
{...}
.then(function (data) {
updateInfo(data)
})
{...}
As the returned data is itself the real data, you don't have to reaccess it with the data property here
{...}
const updateInfo = responseJSON => {
console.log(responseJSON.data)
let keys = Object.keys(responseJSON.data)
console.log(keys)
}
The finished code will look like this -
function getFromSWAPI() {
fetch("https://swapi.dev/api/people/1")
.then(function (response) {
return response.json()
})
.then(function(data){
updateInfo(data)
})
.catch(function(err) {
console.warn(err)
})
}
const updateInfo = responseJSON => {
console.log(responseJSON)
let keys = Object.keys(responseJSON)
console.log(keys)
}
Alternatively, I would suggest you use async/await if you are using fetch inside a function.
There are several things wrong with this code; only one of them is the direct cause of the trouble you're having with logging, but they will all need to be fixed before this will work.
First: why doesn't console.log work / why are you getting TS error "Cannot convert undefined or null to object at Function.keys"?
Because responseJSON does not have a property named "data", because the API response does not include a property named "data". I suspect that name appears here because you're confused by what HTTP responses are and how Promises work. So let's start at the beginning.
fetch returns an HTTP Response object that contains deep within it some text. You access that text by doing:
let text = await response.text()
Or, if you're using chained .then handlers (as in your sample), you do this:
.then((response) => {
return response.text()
})
If you know that the text is valid JSON (and you do), then you can extract the text and parse it into a value with a single operation: response.json() (which is what you are doing).
This returns a Promise that contains the value encoded by the JSON. If the value is an object, it'll have the properties described by that JSON.
What properties will your object have? Open that URL in your browser, and you'll see them: "birth_year", "created", "edited", "eye_color", "films", "gender", "hair_color", "height", "homeworld", "mass", "name", "skin_color", "species", "starships", "url", "vehicles".
"data" is not on that list.
This is what you are doing with the info from the response:
.then(function (response) {
return response.json()
})
.then(function(data){
updateInfo(data)
})
Thus, you are passing the parsed value directly to the updateInfo function.
The updateInfo function then tries to log the .data property on the received value (which does not exist). That is, .data is undefined.
It is not an error to log an undefined object. But Object.keys will throw if provided with undefined:
Object.keys(undefined)
//> Uncaught TypeError: can't convert undefined to object
The fix is to remove .data from both the log statement and the Object.keys call.
Second: that should have been your first debugging step. Even without understanding all the above: if operations are failing on datapoints buried within a value, the most obvious first debugging step is to "take a step back" and try to examine the variable you're working with.
To illustrate: if console.log(myThing.items[0].parent.nodeName) fails, you should immediately try console.log(myThing) -- this allows you to inspect the myThing variable so you can manually verify whether the path you're accessing is legitimate. One of the most common mistakes made by devs of all experience levels is that they put in a bad data path, simply because to err is human. (Typescript will help you notice this while you're writing code, but you must learn how to trace problems without the help of a tool, or you will always need that tool.)
Third: As I mentioned in the first draft of this post, you're missing some return statements.
Most importantly, your getFromSWAPI function does not return anything. You have to return the fetch if you want callers to receive the data.
function getFromSWAPI() {
return fetch("https://swapi.dev/api/people/1")
// ...rest of chained thens
Also, you need to return something inside the .then handler where you're calling updateInfo. What you return depends on what the purpose of updateInfo is:
if updateInfo is supposed to modify the raw API data for the benefit of downstream code, then you should return the result of calling it:
.then(function(data){
return updateInfo(data)
})
if updateInfo is supposed to cause some kind of side-effect (like updating a local cache with the raw data, or firing an event, etc), then you may want to "bypass" the function: call it, but forward the original value to downstream code:
.then(function(data){
updateInfo(data) // could do _anything_ with data
return data // passes the original data onward
})
Unsolicited code review
You're defining one function using the function keyword, and defining the other as a const arrow function:
function getFromSWAPI() { /* stuff */ }
const updateInfo = responseJSON => { /* stuff */ }
Both patterns work fine, but you should try to be consistent. My advice: if you're still learning JS, prefer the function keyword, because it's more explicit.
Prefer to use async/await instead of chained handlers
You can define functions as async and then await only the specific operations that are asynchronous. In your case, updateInfo does not appear to do any async work, so it kind of sucks that it has to live inside this three-part chained promise. I'd go with this:
async function getFromSWAPI() {
let response = await fetch("https://swapi.dev/api/people/1")
let data = await response.json()
updateInfo(data)
return data
}

How to define a variable globally in TypeScript?

I am trying to set a value globally but not able to solve. Here is my code
declared variable - allViewounters: any[] = [];
viewgraph() {
let formData: FormData = new FormData();
this.profileService.lineChartData(formData).subscribe((response) => {
this.allViewounters = response.allViewounters;
console.log(this.allViewounters);
});
}
I want to print the value outside the function console.log(this.allViewounters);
How can I get the value? I tried to get the value through "storageService" but in 1st load, it is not any giving value, for 2nd load it works fine, but I need in 1st page load.
So the condition that you have right now is that you have a callback function that needs to get passed as variable on subscribe(). Since you want to pass the callback result somewhere else (outside viewgraph()), you can set an outside variable to the result that you're getting (what you have done). However, the variable will only be set if the callback has been called (the condition that you have right now).
However, you want to access the value once the callback is done. There is a way in JS/TS to "wait" for a result, by wrapping it in a promise and await for the result. So let's say the library has this method:
function subscribe (callbackFn: (response: any) => void) {
// Assuming response gets passed from somewhere else
callbackFn(response);
}
However, you want to wait for this callback to be called. You can wrap the way you handle the result by using Promise in an asynchronous function:
async function viewgraph() {
let formData: FormData = new FormData();
return new Promise((resolve, reject) => {
this.profileService
.lineChartData(formData)
.subscribe((response) => {
resolve(response);
}
});
}
and then on whatever function that you want to obtain the result, call this function with await, like:
const response = await viewgraph();
Keep in mind that whatever function that calls an await needs to be asynchronous too (marked as async, just like viewgraph()).
If you want to learn more about promise handling and async/await, read this article.

Changing the state with returned JSON data

i am trying to get data from an external API and use it to change the state on my app. The data actually shows up in the console but when i run set state does not change the state of my app.
class App extends Component {
state={
jobs:[]
}
onTermSubmit=(term)=>{
const proxy=`https://cors-anywhere.herokuapp.com/`;
const api = `${proxy}https://--------/positions.json?description=${term}&page=1`;
fetch(api).then(res=>res.json()).then(data=>this.setState({jobs:data}))
console.log(this.state)
}
I am trying to get the state to change to the data returned from the API
Both fetch() and setState() are asynchronous so attempting put a console.log() statement after the expression will not wait for the fetch or setState() to complete/resolve. Instead you can use the callback argument of setState() to console.log() only after fetch has resolved and setState() has updated state. From the documentation:
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
fetch(api)
.then(res => res.json())
.then(data =>
this.setState({ jobs: data }, () => {
console.log(this.state);
})
)
.catch(err => console.log(err));
Hopefully that helps!
As mentioned by #Alexander Staroselsky fetch() and setState() are asynchronous operations and the application won't wait until they are done to continue to the next operation thus resulting in this.state.jobs to still be an empty array.
My solution would be to make them synchronous by adding the async/await keyword before calling he function like this.
onTermSubmit= async (term)=> {
const proxy=`https://cors-anywhere.herokuapp.com/`;
const api = `${proxy}https://--------/positions.json?description=${term}&page=1`;
let rawRes = await fetch(api)
let jsonRes = await rawRes.json()
await this.setState({jobs:rawRes}))
console.log(this.state)
}
Just another approach
Hope this is helpful

Angular2 Can't get data from response using promise?

I'm trying to get data out of response but I can't seem to get the result I want.
facebookLogin(): void {
this.fb.login()
.then((res: LoginResponse) => {
this.accessToken = res.authResponse.accessToken;
this.expiresIn = res.authResponse.expiresIn;
this.signedRequest = res.authResponse.signedRequest;
this.userId = res.authResponse.userID;
console.log('Logged In', res, this.accessToken); //works without problem
this.router.navigate(['../other-register']);
})
.catch(this.handleError);
console.log(this.accessToken) //printing 'undefined'
}
Here within then => { }, console.log seems to print the data in res without any problem. I can see data I want but when I console.log outside of then =>{ }, it's giving me undefined.
what am I doing wrong? I need to use data inside response and pass them to other component but I can't seem to figure out what I'm doing wrong.
Can anyone help me? Thanks
This is the expected behavior actually.
this.fb.login() is a Promise. This means that the value of the result/response (res) will not readily be available right when it is called, but it 'promises' that it will have a value once some action is taken or a response is returned and 'then' it will do something. In this case that action would be connecting to the Facebook API and having data returned. This is just like Ajax in jQuery if you have experience with that, Promises are a more evolved version of callbacks.
What is happening is that you function is being executed in this order:
this.fb.login() is called. Doesn't have a value yet so it allows the script to continue.
console.log() is called.
this.fb.login's value is returned and the then() closure is executed.
If you want to know when the value is return or perform a specific action once it is returned you can call a function within .then() or look into observables (RxJS) to notify other parts of your application that login was successful (or wasn't).
Observables Example
Here is one example on Observables, however, I would do more research as there are multiple Subjects to select from, all which have slightly different behavior. Also, this kind of pattern works better in Angular2+ if this is performed in a service, that way other components will be able to access the information provided by Facebook.
import { AsyncSubject } from 'rxjs/AsyncSubject';
// ...
response: AsyncSubject<any> = new AsyncSubject();
facebookLogin(): void {
this.fb.login()
.then((res: LoginResponse) => {
this.response.next(res);
this.response.complete();
this.router.navigate(['../other-register']);
})
.catch(this.handleError);
}
You then retrieve the data from within response like this:
this.response.subscribe(result => {
console.log(result);
})
Pass Data Example
Since you already have a function in the service designed to receive the data, this may be a wiser implementation in your case.
facebookLogin(): void {
this.fb.login()
.then((res: LoginResponse) => {
this.user_service.passData(res.authResponse.accessToken);
this.router.navigate(['../other-register']);
})
.catch(this.handleError);
}

Call Angular2 functions in series, one after the other

I have a service in an Angular2 project that takes some parameters and returns a value list to populate drop down menus on a form. When the form component initializes, I need to call the same service multiple times with different parameters to define a number of different dropdown menus, however if I call them all, the last one called clobbers the previous ones, presumably because the subsequent calls are overriding or cancelling the previous fetches.
I've split each of the calls into their own function, but I need a way to call each function in series so that the second doesn't get called until after the first completes. Each function works on its own, however if I call more than one, only the last one succeeds, and the first fail with errors (as the service itself terminates the current fetch when its called with new parameters before finishing).
this.fetchValueListOne();
this.fetchValueListTwo();
this.fetchValueListThree();
I was trying to make this work with promises, but wound up in scoping hell pretty quickly with having to pass the services I wanted to access into the functions and then not being able to get the resulting data back out again - each service call takes three parameters and then sets a specific this.valueList[] variable defined in the component and used on the form.
I also tried creating a list of the functions as variables and then iterating over them, however I ran into the same scoping issues as with promises.
The service returns an Observable, the functions subscribe to that Observable, retrieve the data and assign it to a array variable in the component that a dropdown value list is bound to.
The functions look like this:
fetchValueListOne() {
this.dataSvc.getValueList('Val-List-One', this.stateSvc.currentContext, this.stateSvc.currentLanguageCode)
.map(response => response.json())
.subscribe(
data => {
this.valListOne = data;
},
err => console.log('Error', err),
() => {
console.log('this.valListOne', this.valListOne);
}
);
}
SrAxi pointed me in the right direction, and ultimately I solved the problem as follows where Promises turned out to be the best solution, specifically the Promise / .then mechanism solved the problem nicely.
fetchValueList(listCode): Promise<any> {
return this.dataSvc.getValueList(listCode, this.stateSvc.currentContext, this.stateSvc.currentLanguageCode)
.map(response => response.json())
.toPromise();
}
initializeDropDowns() {
this.fetchValueList('First-Val-List')
.then(data => {
this.firstValList = data;
return this.fetchValueList('Second-Val-List')
}).then(data => {
this.secondValList = data;
return this.fetchValueList('Third-Val-List')
}).then(data => {
this.thirdValList = data;
}) }
I defined the functions in the component, and then called initializeDropDowns() in ngOnInit.
The fetchValueList function returns a Promise, so the first call passes the first listCode and when the Promise resolves, the return value is in the data variable in the .then block where we can assign it to the this.firstValList variable. As the function has returned data, we know the service has finished and it's safe to call again with the second listCode, the return value is in the data variable in the next .then block and we assign it to the this.secondValList variable.
We can chain this as many times as required to populate all the variables, and on the last code block we simply omit the return statement and the block terminates.
This is a very specific use case where we have a single service that needs to be called multiple times as the component initializes, and where the service has to complete its fetch and return a value before it can be called again, but in this case, the Promise / .then method was ideal.
Call the functions when you received the data. Such as:
this.fetchValueListOne().subscribe((firstData) => {
this.fetchValueListTwo(firstData);
// Do something with firstData
}
);
this.fetchValueListTwo().subscribe((secondData) => {
this.fetchValueListThree(secondData);
// Do something with secondData
}
);
this.fetchValueListThree().subscribe((thirdData) => {
// Do something with thirdData
}
);
And declare these functions as Observable, such as:
public fetchValueListOne(): Observable<any> { // Get firstData }
public fetchValueListTwo(): Observable<any> { // Get secondData}
public fetchValueListThree(): Observable<any> { // Get thirdData}
This way you will be certain that when you call a function you have the data from the previous one.

Categories

Resources