Having an array of this form:
[{"game_id":4,"city":"London","year":2018,"athlete_id":"1"},
{"game_id":2,"city":"Paris","year":2016,"athlete_id":"2"}]
it is received from back-end and stored like this:
callAPI() {
fetch('http://localhost:9000/testAPI')
.then((res) => res.text())
.then((res) => this.setState({ apiResponse: res }));
}
and then, in the render is send as props to the Table component:
render() {
return (
<div className='App'>
<header className='App-header'>
<Table data={this.state.apiResponse} />
</header>
</div>
);
}
The problem comes here when I want to send to the table only parts of apiResponse.
This is the component:
class Table extends React.Component {
constructor(props) {
super(props);
}
getGames = function () {
return this.props.data;
};
render() {
return (
<div>
<table>
<thead>
<tr>{this.getGames()}</tr>
</thead>
</table>
</div>
);
}
}
The above code sends all the data, so I tried to send only the data that I want, for example only the keys and make headers out of them.
So I replaces the content of getGames() with this:
getGames = function () {
return Object.keys(this.props.data[0]);
};
but it throws this error:
TypeError: Cannot convert undefined or null to object
What I want is to create a table with headers: game_id, city, year, athelete_id and their columns the show their corresponding data.
Related
TableHeader and TableBody component render twice after fetching data from API due to Table row are rendered twice and given duplicate key error in Reactjs.
Error : Each child in a list should have a unique "key" prop.
enter image description here
class Table extends React.Component {
state = {
headers: [],
accesors: [],
data: [],
loading: true
};
componentDidMount() {
instance.get('UserRole/GetDataList')
.then(response => {
var data = JSON.parse(response.data);
this.setState({
headers: Object.keys(data[0]),
data: data,
loading: false
}, () => this.setAccesors());
}, error => {
console.log(error);
});
}
render() {
const { headers, accesors, data } = this.state;
if (this.state.loading ) {
return "Loading...."
}
else {
return (
<table id="datatable-responsive" className="table table-
striped table-bordered">
<TableHeader headers={headers} />
<TableBody data={data} />
</table>
);
}
}
}
export default Table;
So the goal is to fetch data from the google books API, which returns JSON data in the same form as my state shows. I want to update title with the title string returned by the JSON data. Right now I get a "failed to compile" on the line I've marked in the code. Then, I would like to pass the title as a props to the List component, which would render it as a list item with each map through. So if 20 books' data are fetched, I would render 20 different titles. I'm new to react so I'm not sure how much is wrong here.
import React, { Component } from 'react';
import List from './List.js';
export default class Main extends Component {
state ={
items : [{
volumeInfo : {
title : "",
}
}]
}
componentDidMount() {
fetch(`https://www.googleapis.com/books/v1/volumes?q=flowers+inauthor:keyes&key=AIzaSyAWQ0wFzFPQ3YHD_uLDC7sSs-HPRM3d__E`)
.then(res => res.json())
.then(
(result) => {
this.setState({
items : [{
volumeInfo : {
title : result.items.map((book) => {
const name = book.volumeInfo.title;
return name;
})
}
}] });
})
}
render() {
return (
<div>
<header>
<h2>Google Book Search</h2>
</header>
<List title={this.state.items}/>
</div>
)
}
}
Here's List.js
import React, { Component } from 'react'
export default class List extends Component {
render() {
return (
<div>
<ul>
<li>{this.props.items}</li>
</ul>
</div>
)
}
}
As the result of your fetch() has the same structure as your items property of the state, all you need to do in the then() callback is to set the result in the state directly as shown below:
componentDidMount() {
fetch('your/long/url')
.then(res => res.json())
.then((result) => {
this.setState({ items: (result.items || []) });
});
}
Now that your state is updated with the needed data, you need to pass it as a prop to your List component:
render() {
return (
<div>
<header>
<h2>Google Book Search</h2>
</header>
<List items={ this.state.items } />
</div>
);
}
Finally, in your List component, you can make use of this prop by rendering it in a map() call:
render() {
return (
<div>
<ul>
{ this.props.items.map((book, i) => (
<li key={ i }>{ book.volumeInfo.title }</li>
)) }
</ul>
</div>
);
}
export default class Main extends Component {
state ={
items : []
}
componentDidMount() {
fetch(`https://www.googleapis.com/books/v1/volumes?q=flowers+inauthor:keyes&key=AIzaSyAWQ0wFzFPQ3YHD_uLDC7sSs-HPRM3d__E`)
.then(res => res.json())
.then((result) => {
const titleList = result.items.map((item)=>{return item.volumeInfo.title});
this.setState({items: titleList})
})
};
render(){
const {items} = this.state;
const titleComponent = items.length > 0
? items.map((item)=>{
return <List title={item} />
})
: null;
return (
<div className="App">
<header>
<h2>Google Book Search</h2>
</header>
{titleComponent}
</div>
)
}
}
Above code should be worked if your List component is working fine.
Change the setState function with this
this.setState({
items : [{
volumeInfo : {
title : result.items.map((book) => {
const name = book.volumeInfo.title;
return name;
})
}
}] });
Looks like brackets was the issue.
I am able to fetch REST API where I can get nested json output, and I want them to display in React component. Now I only can render them in the console which is not my goal actually. I am wondering if there is an efficient way to do this for rendering nested json list in React. can anyone give me a possible idea to make this work?
here is what I did:
import React, { Component } from "react";
class JsonItem extends Component {
render() {
return <li>
{ this.props.name }
{ this.props.children }
</li>
}
}
export default class List extends Component {
constructor(props){
super(props)
this.state = {
data: []
}
};
componentDidMount() {
fetch("/students")
.then(res => res.json())
.then(json => {
this.setState({
data: json
});
});
}
list(data) {
const children = (items) => {
if (items) {
return <ul>{ this.list(items) }</ul>
}
}
return data.map((node, index) => {
return <JsonItem key={ node.id } name={ node.name }>
{ children(node.items) }
</JsonItem>
});
}
render() {
return <ul>
{ this.list(this.props.data) }
</ul>
}
}
<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
my current output:
in my above component, I could render nested list on the console like this:
[![enter image description here][1]][1]
desired output:
how can I properly render out nested json output on React? Any idea to make this happen? any thought? Thanks
As you knew .map() is the common solution for this. But you can make this much better like below.
export default class List extends Component {
constructor(props){
super(props)
this.state = {
data: [],
isLoaded: false, //initally the loading state is false.
}
};
componentDidMount() {
fetch("/students")
.then(res => res.json())
.then(json => {
//updating the loading state and data.
this.setState({data: json, isLoaded:true});
});
}
render() {
//Waiting ajax response or ajax not yet triggered.
if(!this.state.isLoaded){
return(<div>Loading...</div>);
}else{
//Rendering the data from state.
let studenDetails = this.state.data.map((student, i) => {
let uin = student.uin;
let studentInfo = Object.keys(student.studentInfo).map((label, i) => {
return (
<div key={i}>
<span>
<strong>{label}: </strong>{`${student.studentInfo[label]}`}
</span>
</div>
);
});
return (
<div key={i}>
<h3>{uin}</h3>
<p>{studentInfo}</p>
</div>
);
});
return (<div>{studenDetails}</div>);
}
}
}
Hope it will help you.
To render a list in react use the .map() function to build a list of jsx elements.
render() {
let myRenderedData = this.state.data.map((x, index) => {
return <p key={index}>{x.uin}</p>
})
return (<div>{myRenderedData}</div>)
}
Total beginner with React.
I am trying to work out the standard approach to this situation in React.
I am accessing an api, the data is being returned all ok, except I am trying to set the data as a state of my component, and the render() method is referencing the state before any data is returned so the state property is being defined as 'null'.
In my code sample below you can see I am logging to the console, and despite the order of things, the second log is being returned from the browser before the one that has setState to be the API data.
Any help / explanation as to why this is happening despite using .then() would be appreciated.
Thank you.
PS: I have removed the TeamList component for simplification, but like the 'second log', the component gets rendered before the data has actually been pulled in.
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
}
}
componentDidMount() {
const uri = 'http://api.football-data.org/v2/competitions/PL/teams';
let h = new Headers()
h.append('Accept', 'application/json')
h.append('X-Auth-Token', 'XXXXXXXXXXXXXXXXXXXX')
let req = new Request(uri, {
method: 'GET',
headers: h,
mode: 'cors'
})
var component = this;
fetch(req)
.then( (response) => {
return response.json()
})
.then( (json) => {
this.setState({ data: json })
})
.then( (json) => {
console.log( 'second log', this.state.data )
})
.catch( (ex) => {
console.log('parsing failed', ex)
})
console.log( 'first log', this.state.data )
}
render() {
return (
<div>
<div className="App">
<TeamList list={this.state.data} />
</div>
</div>
);
}
}
export default App;
You need to add something like this to the start of your render():
if (this.state.data === null) {
return false;
}
So your code should be:
render() {
if (this.state.data === null) {
return false;
}
return (
<div>
<div className="App">
<TeamList list={this.state.data} />
</div>
</div>
);
}
render() is called immediately, but you want it to return false until this.state.data has data
When you mount a component, it gets rendered immeadiately with the initial state (that you've set in the constructor). Then later, when you call setState, the state gets updated and the component gets rerendered. Therefore it makes sense to show something like "loading..." until state.data is not null:
render() {
return (
<div>
<div className="App">
{this.state.data ? <TeamList list={this.state.data} /> : "loading..." }
</div>
</div>
);
}
Now additionally logging does not work as expected as setState does not return a promise, so:
.then( (json) => {
this.setState({ data: json })
})
.then( (json) => {
console.log( 'second log', this.state.data )
})
is actually the same as:
.then( (json) => {
this.setState({ data: json })
console.log( 'second log', this.state.data )
})
and that still logs null as setState is asynchronous, which means that calling it does not change this.state now but rather somewhen. To log it correctly use the callback:
then( (json) => {
this.setState({ data: json }, () => {
console.log( 'second log', this.state.data )
});
})
Just an idea:
import React, { Component } from 'react';
class App extends Component {
constructor(props)
{
super(props);
this.state = {
data: null,
};
}
componentDidMount()
{
fetch('http://api.football-data.org/v2/competitions/PL/teams')
.then(response => response.json())
.then(data => this.setState({ data }));
}
render() {
return (
<div>
<div className="App">
<TeamList list={this.state.data} />
</div>
</div>
);
}
}
export default App;
TeamList :
class TeamList extends React.Component {
constructor(props) {
super(props);
}
render(){
return (
<ul>
{
this.props.list.map((element, i) => {
return (
<li className="un-res t_d " key={i}>{element}</li>
)
}
})
}
}
export default TeamList
Happy coding!
I'm making an Rails API call and return a single JSON object that has a nested User Object and a tag list array. However, I can't access the nested Object.
this.props.post.user.name throws:
Cannot read property 'name' of undefined.
I am confused because when I make the call to PostsIndex in PostsIndex.js and get an array of objects and map through it I can access everything.
Is there something I need to do when only dealing with a single object?
PostShow.js
import React, {Component} from 'react';
import axios from 'axios';
import {Link} from 'react-router-dom';
export default class PostShow extends Component {
constructor(props) {
super(props)
this.state = {
post: {}
};
}
componentDidMount() {
const { match: { params } } = this.props;
axios
.get(`/api/posts/${params.postId}`)
.then(response => {
console.log(response);
this.setState({ post: response.data});
})
.catch(error => console.log(error));
}
render() {
return (
<div>
<Post post={this.state.post}/>
</div>
);
}
}
class Post extends Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
<div className="centered">
<small className ="small" > | Posted by: {this.props.post.user.name} on | Tags: </small>
<h3>{this.props.post.title}</h3>
<img className="image " src={this.props.post.image}/>
</div>
<div>
<p className = "songTitle"> {this.props.post.song_title} </p>
<p className= "postBody"> {this.props.post.body} </p>
<div className = "link" dangerouslySetInnerHTML={{ __html: this.props.post.link }} />
</div>
</div>
);
}
}
Here is what the JSON object looks like from /api/posts/7:
{"id":7,
"title":"adgaadg",
"body":"adgadgagdgd",
"post_type":"Video",
"tag_list":["ERL"],
"image":"/images/original/missing.png",
"song_title":"adgdgdgd",
"created_at":"2018-08-11T21:57:00.447Z",
"user":{"id":2,"name":"John","bio":"bio","location":"Reno"}}
That's because this.props.post.user will be undefined before your request has finished, and trying to access name on that will give rise to your error.
You could e.g. set the initial post to null and not render anything until your request is complete.
Example
class PostShow extends Component {
constructor(props) {
super(props);
this.state = {
post: null
};
}
componentDidMount() {
const {
match: { params }
} = this.props;
axios
.get(`/api/posts/${params.postId}`)
.then(response => {
console.log(response);
this.setState({ post: response.data });
})
.catch(error => console.log(error));
}
render() {
const { post } = this.state;
if (post === null) {
return null;
}
return (
<div>
<Post post={post} />
</div>
);
}
}
axios.get is an async operation and <Post post={this.state.post}/> renders before this.setState({ post: response.data}); which means when Post component renders this.state.post is empty object. So what you can do is, initialize your post with null in constructor
this.state = {
post: null
};
and instead of <Post post={this.state.post}/> do {this.state.post && <Post post={this.state.post}/>} it will render post only if its exists and not null.