this.props.stats.map is not a function - reactjs

In the console i am receiving back data from an API: https://github.com/SunDwarf/OWAPI/blob/master/api.md
but in the file where i am trying to render it to the page i am getting back an error : this.props.stats.map is not a function
previously i did it like this for a weather app and didn't have a problem so i am not sure where to look to figure this out.
*EDIT: added curly brackets to function mapStateToProps({ stats }) which now changes the error to "TypeError: Cannot read property 'achievements' of undefined"
does this mean the path to the info is incorrect?
Code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
class OverwatchStats extends Component {
renderStats(achievementData) {
return (
<tr>
<td>{achievementData.data.eu.achievements.support}</td>
</tr>
);
}
render() {
return (
<table className="table table-hover">
<thead>
<tr>
<th>Hero Achievements</th>
<th>Got</th>
</tr>
</thead>
<tbody>
{this.props.stats.map(this.renderStats)}
</tbody>
</table>
);
}
}
function mapStateToProps({ stats }) {
return { stats };
}
export default connect(mapStateToProps)(OverwatchStats);
I have my code here: My Github

According to the api, stats is the object, not the array, so you can't map over it

Eldar G hit the nail right on the head.
Because stats is an object that outputs the following
var stats = {"stats": {
"typeofstat" : {},
"othertypeofstat": {}
...
}
You can do the following in order to iterate all keys of an object.
Object.keys(this.props.stats).map((key) => {
var currentStat = stats[key];
//do stuff with currentStat
});
Read https://github.com/SunDwarf/OWAPI/blob/master/api.md#get-apiv3ubattletagstats if you want more information on the type of keys that each stat contains. It seems your response will be a deeply nested object. Interpret and manipulate such an object according to your preferences and requirements.
Hope this helps. Remember, all iterator methods like map, forEach, reduce, etc... work only for arrays!

Related

How to exclude certain keys/values when iterating through array and displaying on table in Angular

Have just run into a bit of a roadblock and any advice would be much appreciated
I am currently building out an application in angular to fetch data from an API and display it on the page
I have a service called "Api Service using the HTTPClient to call the API
apiservice.service.ts
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class ApiserviceService {
constructor(private _http:HttpClient) { }
getData(){
return this._http.get('APIURL');
}
}
Then I have another component to observe this call and map the data to an array
data.component.ts
import { Component, OnInit } from '#angular/core';
import { map, toArray } from 'rxjs';
import { ApiserviceService } from '../apiservice.service';
#Component({
selector: 'app-data',
templateUrl: './data.component.html',
styleUrls: ['./data.component.scss']
})
export class VenueComponent implements OnInit {
data: any = [];
constructor(private service:ApiserviceService) {}
MapData() {
this.service.getData().subscribe(result=>{
this.data=result;
console.log(this.data);
})
}
ngOnInit() {
this.MapData()
}
}
I am then displaying the data in a table
data.component.html
<table>
<tr *ngFor= "let item of data | keyvalue" >
<td>{{item.key}} : {{item.value}}</td>
</table>
Now at the moment I am iterating thrugh the array using *ngFor but there are keys/values that I want to exclude from being displayed
For example I want to display key2 and value 2 but not key 1 and value 1
[Key1 : value1], [Key2 :value2]
How would I go about doing this? Any advice would be much appreciated
in my opinion you have at least 2 options, filter the observable:
this.service.getData().pipe(
filter(o => {
return // your condition here
})
).subscribe(result=>{
the second option is to write a custom pipe. You can create a pipe that extend the keyvalue pipe and filters the data as you need. You will find numerous tutorial where they explain how to create a pipe.
I think you can use Angular Slice Pipe[1]
<table>
<tr *ngFor= "let item of data | keyvalue | slice: 1" >
<td>{{item.key}} : {{item.value}}</td>
</table>
Hope this help, Thanks!
[1]: https://angular.io/api/common/SlicePipe
First of all: use types for your variables. It gives you the benefit of knowing exactly which kind of data you are handling and it helps us to understand what exactly you want to achieve.
Here is my take on this: I suggest you to put this kind of logic into your service, so that this logic can be reused somewhere else.
I also suggest you to use some kind of reactive approach, so that you don't have to manually unsubscribe your requests.
The following approach is based on the assumption that your API returns an array, not an object.
apiservice.service.ts
...
export class ApiserviceService {
data$ = this._http.get('APIURL').pipe(
map(data => data.filter(item => item.key !== 'Key1'))
);
constructor(private _http:HttpClient) { }
...
data.component.ts
...
public data$ = this.service.data$;
...
data.component.html
<table>
<tr *ngFor= "let item of data$ | async" >
<td>{{item.key}}</td>...
</table>
I suggest you to not use pipes in this case, since pipes should be generic and your filtering logic seems to be too specific for a pipe.

Angular return me an Array as undefined, but I had defined previously

I'm trying to use Google Books API. Firstly I created the interfaces that worked properly, then I created a method which return me an Array with a list of Books.
public getBooks(): Observable<Book[]>{
return this.http.get<Book[]>('https://www.googleapis.com/books/v1/volumes?q=filibusterismo');
}
(I had added the element which I want to search, "filibusterismo", in order to avoid mistakes, but I would change later.)
Then, I want to use the Array of Book, but I can not use a ngFor loop because getBook return me an observable Array of Books instead of an Array of Books. This was my original constructor
books:Book[];
constructor(private GoodreadsService:GoodreadsService) { }
ngOnInit() {
this.libro=this.GoodreadsService.getBooks();
}
I had tried to fix this using async.
*ngFor="let book of books | async"
But I have the same problem. So reading post in SO I learnt that I have to use subscribe in the ngOnInit(), like this.
this.GoodreadsService.getBooks().subscribe(res=>{this.books=res},
error => { // on failure
console.log('Error Occurred:');
});
The problem, is that whereas I does not return me any kind of error, if I write
console.log(this.libro1);
The console say that the array is undefined, whereas I had defined it as an array of Book. Furthermore, I still can not use an ngFor.
I also had tried to map the getBook method, in order that return me an Array instead of an observable array, but I could not do it in spite of reading a lot of question in SO.
So, would somebody tell me how can I fix this issue, in order I could use an ngFor in the HTML?
My HTML is this
<p>busqueda works!ss </p>
<table>
<thead>
<th>Name</th>
<th>Indexs</th>
</thead>
<tbody>
<div *ngFor="let book of books | async">
<h1 >
<p>prueba</p>
</h1>
</div>
</tbody>
</table>
Here's how you need to do it. Initialize the API call and subscribe to accordingly in order to send the request. After the response is returned then iterate over the items over it.
Please refer to the working sample stackblitz attached here.
Sample component.ts :
import { Component, OnInit } from "#angular/core";
import { SampleService } from "./sample.service";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit {
books: any;
constructor(public sample: SampleService) {}
ngOnInit() {
this.sample.getBooks().subscribe(data => {
console.log(data.items)
this.books = data.items;
});
}
}
Sample HTML :
<table>
<thead>
</thead>
<tbody>
<div *ngFor="let book of books">
<h4>
<p>kind : {{book.kind}}</p>
<p>Id : {{book.id}}</p>
<p>etag : {{book.etag}}</p>
</h4>
</div>
</tbody>
</table>

Copy the HTML table to clipboard in reactjs

I have an HTML table in my react project. I want to copy the table to clipboard.
<table id="sample">
<thead>
<th>Amount</th>
<th>Charges</th>
</thead>
<tbody>
<tr>{item.Amount}</tr>
<tr>{item.Charges}</tr>
</tbody>
</table>
This is the table structure . I haven't included any react code.
I referred this sample code https://stackblitz.com/edit/js-copy-element?file=index.js . But it's not in react.
Using react code how can I copy the table to clipboard?
might be too late to answer this, but here's how I did it.
I had element rendered in my component's render function.
render(){
return(
<table id="table">
.... your table content
</table>
)
}
and simply build a copy function by referring to the sample code shared above.
copyTable = () => {
const elTable = document.querySelector('table');
let range, sel;
// Ensure that range and selection are supported by the browsers
if (document.createRange && window.getSelection) {
range = document.createRange();
sel = window.getSelection();
// unselect any element in the page
sel.removeAllRanges();
try {
range.selectNodeContents(elTable);
sel.addRange(range);
} catch (e) {
range.selectNode(elTable);
sel.addRange(range);
}
document.execCommand('copy');
}
sel.removeAllRanges();
console.log('Element Copied! Paste it in a file')
}
then, call this function by building an element like this
<h1 onClick={()=>{this.copyTable()}}>Copy to table</h1>

Iterate with ngFor on objects obtained from Firebase

I have a list of objects stored in firebase database:
I am getting this list using angular http get request. After getting it I want to iterate on a li in html template using ngFor="let subject of subjects" which gives Error:
ERROR Error: Cannot find a differ supporting object '[object Object]'
of type 'object'. NgFor only supports binding to Iterables such as
Arrays.
As I searched online, I came to know that ngFor can only be used on arrays while I am getting nested objects. Can anyone please suggest me what should I do iterate these objects.
So far I have tried Array.of which converts whole list into someArray[0]. I also tried to manually change the unique ids of objects into array indexes [0, 1, 2] which worked but when I add new subject using http post firebase automatically assigns unique id to new subject making it uniteratable.
In simple words tell me to convert nested objects into an arrayList or how can I change the firebase default behavior of assigning unique id in angular (I found something like firebase push function which I couldn't understand).
Update:
(Code from ExaminerService)
getBatchSubjects(batch){
return this.http.get(firebaseLinkGoesHere);
}
(Code from Component)
onBatchSelected(event){
this.selectedBatch = event.target.value; //getting value of a html select
this.examinerService.getBatchSubjects(this.selectedBatch)
.subscribe(
(response : Response) => {
this.selectedBatchData = response.json();
}
),(error) => {
console.log(error);
}
}
(Code from HTML Template)
<div class="batchDetails">
<table class="table table-striped">
<thead>
<tr>
<th class="text-center">S.No</th>
<th>Subject Name</th>
<th>Facilitator</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let subject of selectedBatchData.subjects; let i = index">
<td class="text-center"> {{i + 1}} </td>
<td> {{subject.name}} </td>
<td> {{subject.facilitator}} </td>
</tr>
</tbody>
</table>
</div>
You can use Object.values to turn the properties into an array. This can be done in several ways.
Method 1 - By applying the map operator to the observable data (see this stackblitz):
import { Observable } from "rxjs/Rx";
public subjects: Array<any>;
this.examinerService.getBatchSubjects(this.selectedBatch)
.map(x => (Object as any).values(x.json().subjects))
.subscribe(
(values) => { this.subjects = values; },
(error) => { console.log(error); }
);
Method 2 - With a property getter or a method of the component class (see this stackblitz):
get subjects(): Array<any> {
return (Object as any).values(this.selectedBatchData.subjects);
}
Method 3 - By converting the values in the subscribe callback (see this stackblitz):
public subjects: Array<any>;
...
this.selectedBatchData = response.json();
this.subjects = (Object as any).values(this.selectedBatchData.subjects);
Template
In the template, the ngFor directive would iterate over the subjects array:
<tr *ngFor="let subject of subjects; let i = index">
<td class="text-center"> {{i + 1}} </td>
<td> {{subject.name}} </td>
<td> {{subject.facilitator}} </td>
</tr>
try following code snippet.
ngFor="let subject of subjects|async
Update
In your ExaminerService you should import FirebaseListObservable in order to define return type FirebaseListObservable<any[]>
import { AngularFireDatabase, FirebaseListObservable } from 'angularfire2/database';
export class ExaminerService{
constructor(private db: AngularFireDatabase) {}
getBatchSubjects(batch){
return this.db.list('/subjects');
}
}
In your Component should look like this
export class ExaminerComponent implements OnInit {
movies: any[];
constructor(private examinerDb: ExaminerService) { }
ngOnInit() {
this.examinerDb.get().subscribe((snaps) => {
this.selectedBatchData = snaps;
});
}
}
Angular has pipe "keyvalue" which will let you parse your object into pairs like subject.key and subject.value

React "Uncaught TypeError: Cannot read property of undefined"

I'm React newbie and struggling. Following snippet gives the following error
"Uncaught TypeError: Cannot read property 'creationDate' of
undefined".
If I move code from populateTableRows and creationDate functions inside render, everything works nicely. SurveyList gets it's data from another component. I know this is pretty ugly component and all other suggestions are welcome too, but I'm mostly interested in this one particular error.
import React from 'react';
import ReactDOM from 'react-dom';
import { Table, Tr, Td } from 'reactable';
class SurveyList extends React.Component{
constructor(props) {
super(props);
this.state = {
isSaved: false,
surveys: []
};
this.creationDate = this.creationDate.bind(this);
this.populateTableRows = this.populateTableRows.bind(this);
}
creationDate (obj){
return new Date(obj._id.time).toISOString().slice(0,10);
}
populateTableRows(surveys){
var surveyRows = [];
surveys.forEach(function(obj){
surveyRows.push(
<Tr key={obj.surveyId}>
<Td column="SurveyId">{obj.surveyId}</Td>
<Td column="Survey name">{obj.surveyName}</Td>
<Td column="Creation date">{this.creationDate(obj)}</Td>
<Td column=""><ModalDialog key={obj.surveyId}
survey={obj}
/></Td>
</Tr>
);
});
return surveyRows;
}
render() {
var surveys = Array.from(this.props.surveys);
var surveyRows = this.populateTableRows(surveys);
return (
<Table className="table" id="table" sortable={true} filterable={['SurveyId', 'Survey name', 'Creation date']}>
{surveyRows}
</Table>
)
}
}
The comment by #ctrlplusb is correct. When you use a function keyword as you have done in your surveys.forEach call, its contents get a new scope - and therefore a new this, which is undefined as it belongs to no object. There are several solutions.
The prettiest is to use the new fat arrow ("lexical this") syntax available in ES2015 through Babel. It creates a function that maintains the scope in which it was defined. E.g.:
surveys.forEach( obj => surveyRows.push(/* ... */) );
However, the simplest is to use the second argument that forEach takes, which is the this to use:
surveys.forEach( function ( obj ) {
surveyRows.push(/* ... */);
}, this );

Resources