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!
Related
class Home extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoaded: false,
};
}
componentDidMount() {
fetch("https://reqres.in/api/users?page=2")
.then((res) => res.json())
.then((json) => {
this.setState({
isLoaded: true,
data: json,
});
});
}
render() {
var { isLoaded, data }= this.state;
if(!isLoaded){
return<div>Is isLoaded</div>
}
else{
return (
<div>
<ul>
{() =>
this.state.data.map((data, index) => (
<li key={index}>Email: {data.email}</li>
))
}
;
</ul>
</div>
);
}
}
}
export default Home;
Hii All , I know this question is asked many times but I cant figure it out I'am getting the error. I have checked for all the questions similar to this but haven't found specific solution if I use another link i.e, "https://jsonplaceholder.typicode.com/users" this one the code works fine .
The returned data from https://reqres.in/api/users?page=2 is not an array, but an object with a data property containing what you are looking for (an array). The result of the request is :
{"page":1,"per_page":6,"total":12,"total_pages":2,"data":[{"id":1,"email":"george.bluth#reqres.in","first_name":"George","last_name":"Bluth","avatar":"https://reqres.in/img/faces/1-image.jpg"},{"id":2,"email":"janet.weaver#reqres.in","first_name":"Janet","last_name":"Weaver","avatar":"https://reqres.in/img/faces/2-image.jpg"},{"id":3,"email":"emma.wong#reqres.in","first_name":"Emma","last_name":"Wong","avatar":"https://reqres.in/img/faces/3-image.jpg"},{"id":4,"email":"eve.holt#reqres.in","first_name":"Eve","last_name":"Holt","avatar":"https://reqres.in/img/faces/4-image.jpg"},{"id":5,"email":"charles.morris#reqres.in","first_name":"Charles","last_name":"Morris","avatar":"https://reqres.in/img/faces/5-image.jpg"},{"id":6,"email":"tracey.ramos#reqres.in","first_name":"Tracey","last_name":"Ramos","avatar":"https://reqres.in/img/faces/6-image.jpg"}],"support":{"url":"https://reqres.in/#support-heading","text":"To keep ReqRes free, contributions towards server costs are appreciated!"}}
So you cannot use map function, which is from the Array prototype, on the result of your request. You must access the data property first :
this.state.data.data.map((data, index) => ( // note the double data
<li key={index}>Email: {data.email}</li>
))
You could also assign json.data to the state.data to avoid the ugly .data.data :
this.setState({
isLoaded: true,
data: json.data, // note the .data
});
I think the problem is in brackets around your .map() method. Please try this
class Home extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoaded: false,
};
}
componentDidMount() {
fetch("https://reqres.in/api/users?page=2")
.then(res => res.json())
.then(json => {
this.setState({
isLoaded: true,
data: json,
});
});
}
render() {
const { isLoaded, data } = this.state;
if (!isLoaded) {
return <div>Is isLoaded</div>;
} else {
return (
<div>
<ul>
{data?.map((data, index) => {
return <li key={index}>Email: {data.email}</li>;
})}
</ul>
</div>
);
}
}
}
export default Home;
I don't see any error, it's working just fine.
Output:
Working Example: StackBlitz
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoaded: false,
};
}
componentDidMount() {
fetch('https://reqres.in/api/users?page=2')
.then((res) => res.json())
.then((json) => {
console.log(json.data);
this.setState({
isLoaded: true,
data: json.data,
email: null,
});
});
}
render() {
var { isLoaded, data } = this.state;
if (!isLoaded) {
return <div>Is isLoaded</div>;
} else {
return (
<div>
<div className="contents home">
<img
src="https://trucard.io/india/wp-content/uploads/2021/08/2021-June-TruCard-Logo.png
"
width={50}
alt="img"
className="trucard-img"
/>
</div>
<div className="button">
<button className="button-button">Load list</button>
</div>
<ul>
{this.state.data?.map((data, index) => (
<li key={index}>Email: {data.email}</li>
))}
;
</ul>
</div>
);
}
}
}
export default App;
I am trying to get get some data from an API using React. Below is one of my components where I am getting the error.
import React, { Component } from "react";
import Filmcard from "./Filmcard";
const url = "http://www.omdbapi.com/?s=star&apikey=4ababda2";
class Filmcardlist extends Component {
constructor() {
super();
this.state = {
films: []
};
}
componentDidMount() {
fetch(url)
.then(resonse => resonse.json())
.then(data => {
this.setState({ films: data });
})
.catch(error => console.log("I have errored" + error));
}
render() {
return (
<div>
{this.props.films.map(film => (
<Filmcard
Title={film.Title}
Year={film.Year}
imdbID={film.imdbID}
Type={film.Type}
/>
))}
</div>
);
}
}
export default Filmcardlist;
I am fairly new to React and I'm not sure why I am getting this error.
change:
this.setState({ films: data });
to:
this.setState({ films: data.Search });
and this.props.films to this.state.films in render function.
Just change props with state and as I read the comments you need to map data.search.
Try this code, hope I helped!
{
if(this.state && this.state.films && this.state.films.search){
this.state.films.search.map(film => (
<Filmcard
Title={film.Title}
Year={film.Year}
imdbID={film.imdbID}
Type={film.Type}
/>
))}
}
I'm trying to render the data from my database get this instead Failed to compile.
./src/components/list-pets.component.js
Line 38:5: Expected an assignment or function call and instead saw an expression no-unused-expressions
Search for the keywords to learn more about each error.enter code here
Here is my code from the trouble component
import React, { Component } from 'react';
import axios from 'axios';
export default class ListPets extends Component {
constructor(props) {
super(props);
this.state = {
pets: []
};
}
componentDidMount = () => {
this.getPets();
};
getPets = () => {
axios.get('http://localhost:5000/pets')
.then((response) => {
const data = response.data;
this.setState({ pets: data });
console.log('Data has been received!');
})
.catch((err) => {
console.log(err);
});
}
displayPet = (pets) => {
if (!pets.length) return null;
return pets.map((pet, index) => {
<div key={index}>
<h3>{pet.name}</h3>
<p>{pet.species}</p>
</div>
});
};
render() {
console.log('State: ', this.state);
return (
<div className='adopt'>
{this.displayPet(this.state.pets)}
</div>
)
}
}
You need to return a value at each pets.map iteration, currently you’re returning undefined.
return pets.map((pet, index) => {
return (
<div key={index}>
<h3>{pet.name}</h3>
<p>{pet.species}</p>
</div>
)
});
You have to wait until fetching data is completed.
You should have to define the loading bar while fetching.
class App extends Component {
constructor() {
super();
this.state = {
pageData: {},
loading: true
}
this.getData();
}
async getData(){
const res = await fetch('/pageData.json');
const data = await res.json();
return this.setState({
pageData: data,
loading: false
});
}
componentDidMount() {
this.getData();
}
render() {
const { loading, pageData } = this.state;
if (loading){
return <LoadingBar />
}
return (
<div className="App">
<Navbar />
</div>
);
}
}
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>)
}
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.