Access nested JSON data in ReactJS when clicking Search? - javascript

I have tried several other options on how to do this, such as
How to read a nested JSON in React?
Can't access nested JSON Objects in React
I am trying to query the API when I click "Search". Currently, when I click "Search", the API queries the correct URL, but I am having trouble accessing the returned data. The data that is sent back looks like this:
{
"option_activity": [
{
"id": "5f033b253c8cf100018a312f",
"bid": "0.6",
"ask": "1.0",
"midpoint": "0.8",
"updated": 1594047269
},
{
"id": "5f033b253c8cf100018a312f",
"bid": "0.6",
"ask": "1.0",
"midpoint": "0.8",
"updated": 1594047269
},
With hundreds of these items. What it looks like in the console:
What I am doing to query the api:
fetchData() {
var val = this.state.searchedValue;
var url = "URL/TO/API"
fetch(url)
.then(res => res.json())
.then((res) => {
const itemsList = res.option_activity;
this.setState({
items: itemsList
},
function () {
console.log(itemsList);
}
);
// console.log(this.state.items)
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
console.log(error);
},
// console.log(this.state.items)
)
}
While the network logs the response from the API, the console log just logs an empty array "[]" for this.state.items, which is initialized to an empty array. How can I save the contents of "option_activity" into an array "items", where I can access the id, bid, ask, midpoint etc through this array of items?
So, items[0] would have items[0].id, items[0].midpoint, etc accessible.
Thanks in advance
Solution is in the comments of the correctly marked answer

Are you hitting an API URL on a different domain? If so, you may well be actually getting a CORS error. If you try to programmatically hit an endpoint on another domain, the browser will check for specific headers to ensure the API allows you to do that. If it doesn't see those headers, it will throw an error (which you should see in your console). The network tab will still show the expected payload, but it won't be made accessible to you in Javascript.
Right now, you are only logging to the console on an error. So, if you are seeing the response in the network tab, but are always ending up in the error handler, that indicates something is wrong with the response (CORS, or somehow maybe you are returning malformed data that can't be parsed as JSON).
You should also note that your current log isn't particularly useful. You are only ever printing out the old value of items. Since you clearly never set it (note that you are in the error handler), you would expect this value to be whatever it was before trying to set the value of items (presumably the initial empty array).

First, you don't need to use map to convert the returned activity list if you want to access to id, bid, etc.
const itemsList = res.option_activity;
this.setState({
items: itemsList
});
Second, if you want to read state right after using setState, you need to use a callback, similar to the following code snippet.
this.setState({ boardAddModalShow: true }, function () {
console.log(this.state.boardAddModalShow);
});
This is because according to React docs
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.

Related

Angular multiple HTTP posts to same API with different request body sends the same request

I have a few graphs of resident care plan, and I use the same component for each graph, that calls the same API, but with different input data. I have a datepicker in a parent component and when this date is updated I want to update all the graphs. Each graph component should call the API with same date and different id, but the calls are being made all equals.
I have a parent component with children components on an *ngFor:
<div *ngFor="let data of selectedItems">
<app-care-plan-graph
[careData]="data"
[dateInput]="dateInput">
</app-care-plan-graph>
</div>
And from each CarePlanGraphComponent I make an API post call:
#Input() careData: {careAdlId:any};
#Input() dateInput: GetAdlEventRequest;
constructor(private residentService: ResidentService) { }
ngOnInit() {
this.getAdlEvents(this.dateInput);
}
getAdlEvents(body: GetAdlEventRequest) {
body.careAdlId = this.careData.careAdlId;
console.log("calling get adl events: ", body);
this.residentService.getAdlEvents(body).then((response: ApiResponseModel) => {
// handle response
})
}
The service call is a common http post:
getAdlEvents(body): Observable<any> {
return this.http.post(this.residentsBaseUrl + 'getAdlEvents', body);
}
On parent component I use ViewChildren to access to the carePlanGraph components and call the method getAdlEvents() on update of the view
#ViewChildren(CarePlanGraphComponent) graphComponents: QueryList<any>;
.
.
.
ngOnInit() {
this.form.valueChanges.subscribe(data => {
this.dateInput= {
startDate: data.startDate,
endDate: data.endDate
};
this.graphComponents.forEach(element => {
element.getAdlEvents(this.selectedInputs);
});
})
}
Everything works fine up to the console.log before making the API post, but the post is being made allways with same request body, no matter that the console log shows that it has different ids.
I can see in the network tab that the payload its the same, here are some images of an example, in the console you can see that there are two different bodys, one with id 60 and other with id 61, but both API post were made with payload with id 61:
1st payload:
1st payload
2nd payload:
2nd payload
Any help on understanding this problem will be appreciated, for now I will move the Api posts to the parent component and use concatMap so that the post are made sequentially, and pass the data to the children, but I would really like to understand why this way is not working.
The reason this is happening is that you're calling the request really with the same body.
Objects in Javascript are passed as a reference to the same obejcts.
Since you pass the object as a body to the API call in the first Graph and then modify the same object in the second Graph, it results in the same api call.
Best thing you can do is copy the body request each time you make the call. This is easily achieved via the spread operator (...).
getAdlEvents(body: GetAdlEventRequest) {
// copy the body and add care id
body = {...body, careAdlId: this.careData.careAdlId};
console.log("calling get adl events: ", body);
this.residentService.getAdlEvents(body).then((response: ApiResponseModel) => {
// handle response
})
}

Realtime Database Transaction does not seem to work when returning objects

I have the following transaction:
db.ref(`path/${id}`)
.transaction((doc) => {
console.log('calling once', doc);
if (!doc) {
console.log('initializing');
return { subdoc: {} };
}
if (!doc.subdoc) {
console.log('initializing subdoc (no inside?)');
doc.subdoc = {};
}
console.log('doc:', doc);
return doc;
})
.then(async (result) => {
console.log('result', result);
const val = await db.ref(`path/${id}`).get();
console.log(val);
});
where db is an instance of firebase.database().
The intention of this transaction is to initialize the document with a subdoc if the correct structure doesn't exist yet.
However, the output I'm getting is the following:
calling once null
initializing
result {"committed": true, "snapshot": null}
null
The transaction update function only gets called once (unlike what's happening in a dozen other questions on StackOverflow), I'm never returning null to abort the transaction, and it's not an eventual consistency problem as when I check the console, the document remains unpopulated.
Further, if I replace the { subdoc: {} } with just a string, the result gets properly set to the string. Even further, if a string is already populated and I try to replace it with an object, the string gets deleted! Why is it that any object gets treated as "set this value to null", and how can I populate it with an object?
If it's relevant, I'm testing this on the Realtime Database emulator rather than a production environment.
Firebase's Realtime Database has essentially no concept of an "empty object", and this cascades upwards. For example,
{
"one": {
"two": {
"three": {}
}
}
}
is fundamentally equivalent to null (which is also equivalent to undefined, or just not having the key at all) because "three" gets set to null, which means "two" is equivalent to {} which also gets set to null, etc.
This can be illustrated by going into the Realtime database console and manually typing in the above structure and seeing that nothing happens when you try to add it. However, if you add something like "bloop": "blop" to "three", the whole structure gets created.
While I feel this is under-documented, it's somewhat internally consistent, so I'm satisfied now that I know this.

Angular Http subscribe - cannot assign to variable in component

I want to retrieve data from api and assign it to some value inside the angular component. In subscribe I'm trying to assign the data to loggedUser and then call function inside this subscribe to navigate to another component with this received object. Unfortunately I got the error : The requested path contains undefined segment at index 1. I want to have this object set outside the subscribe too. How can I achieve this?
logIn() {
this.portfolioAppService.logIn(this.loggingUser).subscribe((data) => {
this.loggedUser = data;
console.log(this.loggedUser);
console.log(data);
this.navigateToProfile(this.loggedUser.Id);
});
}
navigateToProfile(id: number) {
this.router.navigate(['/profile', id]);
}
console output
You are using an incorrectly named property when calling navigateToProfile.
From your console output, I can see that the data object in the subscribe looks like this:
{
id: 35,
// ..
}
But you are calling the function like this:
this.navigateToProfile(this.loggedUser.Id);
Instead, use the property id (lower case)
this.navigateToProfile(this.loggedUser.id);
To narrow this problem down in the future, try being more specific in your testing. Humans are good at seeing what they want to see and will assume the problem is more complicated than it is. If you had tried console.log(this.loggedUser.Id), you would have seen the result undefined, and worked out the problem yourself.

Unable to access element from array of JSON objects

I am trying to access the specific elements of an array of JSON objects. To test I simply have:
{console.log(this.state.stockCharts)}
This returns (in browser):
This is great, but now I want to access a specific element. Say the first element. I type:
{console.log(this.state.stockCharts[0])}
And the browser is like: nah mate
undefined
It's probably something really simple, but I have been banging my head against my keyboard for the past 45 minutes and nothing has worked. Thanks guys!
Edit 1 (For Akrion)
The query that I am using to access the API is:
https://www.alphavantage.co/query?function=TIME_SERIES_WEEKLY&symbol=MSFT&apikey=demo
This is what I get back from the call:
I call this API twice, and after I get a response back I push it to my stockCharts array:
this.state.stockCharts.push(result)
Edit 2 (For Beginner)
I initialize the state how you would normally do it:
this.state = {
stockCharts: []
}
I verified with the api given in my local and I am able to get the data.
First thing the way you push api response to stockCharts is not recommended. Which means direct mutation of the state is not recommended.
You can push api response in the following way
this.setState(prevState => ({
stockCharts: [...prevState.stockCharts, result]
}));
Now in render
render(){
this.state.stockCharts.map((data, i) => {
console.log("data", data); // this will give each object
console.log("Meta Data", data["Meta Data"]); //This will give meta data information
console.log("Weekly Time Series", data["Weekly Time Series"]);// this will print weekly time information
});
return(
)
}
Is this what your expectation?
It might be because you mutate the state which is not recommended.
try instead of calling this.state.stockCharts.push(result) do this.setState({stockCharts: [...this.state.stockCharts, result]})
https://reactjs.org/docs/state-and-lifecycle.html

Angular App Pulling in Entire Collection, Rather Than Finite # of Results as Expected

I am using observables in my Angular app to load data from our API. Because there is a lot to pull in, we're using ng2-pagination to handle pagination, and we're trying to set it up to pull data for each page that loads. So, in other words, initially we'd pull in the first 15 results for when the first page loads. Other pages would load data specific to those pages as well, only once the page number is clicked on.
Now, I can confirm this api call is working because when I run it in Postman, I get a finite set of results - rather than the whole collection - as expected.
However, that said, when I try and plug this in with the observable I'm using in the Angular app, I see that it's still pulling in the entire collection.
This is what I have for the api call (here 'page' represents page number, and 'pagesize' represents results per page):
getByCategory(category: string, page, pagesize) {
const q = encodeURIComponent(stage);
return this.http.get
(`https://api.someurl.com/${this.ver}/clients/category/${q}?apikey=${this.key}&page=${this.page}&pagesize=${this.pagesize}`)
.map((response: Response) => response.json())
.catch(this.stageErrorsHandler);
}
stageErrorsHandler(error: Response) {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
In my component I am subscribing like so, and requesting page 1, with 12 results returned:
ngOnInit() {
this.clientService.getByCategory('consulting', 1, 12)
.subscribe(resRecordsData => {
this.records = resRecordsData;
console.log(this.records);
},
responseRecordsError => this.errorMsg = responseRecordsError);
}
Then, in the view, I am iterating over the array, and passing items through the pagination pipe like this:
<tr *ngFor="let record of records.data | paginate: { id: 'clients', itemsPerPage: 12, currentPage: page, totalItems: records.count }">
<pagination-controls class="paginator" (pageChange)="page = $event" id="clients"
maxSize="15"
directionLinks="true"
autoHide="true">
</pagination-controls>
By the way, the api call is returning the total count as well as the array of objects - because the pagination tool uses that to determine how many pages to load. So the api looks like this - 'data' is the collection of records, and 'count' is the total number of records:
{
"count": 10438,
"data": [
{
"id": "someId",
"name": "someName"
}
]
When I console log (this.records) in the ngOnInit life cycle, what returns is not a finite list of 12 results, as expected, but rather the entire collection. This is what I see in the console:
Object {count: 10728, data: Array(4037)}
I'm not sure why I'm getting the entire collection. As I mentioned, this call works when I try it with Postman - but doesn't appear to be working as expected here.
Any ideas as to what I'm missing here? Is there a problem with how I'm using the observable?
Have you looked at the resulting service call URL in Chrome dev tools' network tab? Does your URL template:
https://api.someurl.com/${this.ver}/clients/category/${q}?apikey=${this.key}&page=${this.page}&pagesize=${this.pagesize}
render correctly? Since you're passing params (category: string, page, pagesize) to the function, try reference them without "this." - e.g. ${page}

Categories