I'm fetching the data from an external API for food recipes, and getting the response in this format (JSON):
{
"count": 30,
"recipes": [
{
"publisher": "BBC Food",
"f2f_url": "http://food2fork.com/view/8c0314",
"title": "Chicken and cashew nut stir-fry",
"source_url": "http://www.bbc.co.uk/food/recipes/chickenandcashewnuts_89299",
"recipe_id": "8c0314",
"image_url": "http://static.food2fork.com/chickenandcashewnuts_89299_16x9986b.jpg",
"social_rank": 95.91061636245128,
"publisher_url": "http://www.bbc.co.uk/food"
},
{
"publisher": "Jamie Oliver",
"f2f_url": "http://food2fork.com/view/0beb06",
"title": "Roasted chicken breast with pancetta, leeks & thyme",
"source_url": "http://www.jamieoliver.com/recipes/chicken-recipes/roasted-chicken-breast-with-pancetta-leeks-and-thyme",
"recipe_id": "0beb06",
"image_url": "http://static.food2fork.com/466_1_1349094314_lrg2129.jpg",
"social_rank": 94.88568903341375,
"publisher_url": "http://www.jamieoliver.com"
},
{ ... more recipes ... }
]
}
And I'm trying to access that data and display, for the purpose of testing, just variables 'count', and the 'publisher' of the first recipe in array. This is my React code:
import React from 'react';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = { data: {} };
}
componentDidMount() {
fetch('https://www.food2fork.com/api/search?key=MY_KEY&q=chicken%20breast&page=2')
.then(response => {
return response.json();
})
.then(jsonData => {
this.setState({ data: jsonData }, function() {
console.log(jsonData);
});
});
}
render() {
return (
<div className="App">
<h1>{this.state.data.count}</h1>
<p>{this.state.data.recipes[0].publisher}</p> // Why this doesn't work?
</div>
);
}
};
If I remove the 'p' tag in the render() function, everything works as expected: the page loads at first, and then after fetching the data, displays '30' as 'h1'.
However, if I run the code with the 'p' tag, I get this error:
I'm searching for the answers for more than two hours and really can't find the answer. Why can I access the variable 'count', but not the variable 'publisher', which is inside of an array? I'm event logging out this.state after setting it, and object looks completely normal there. How can I access the elements in the 'recipes' array?
This is is because when you are fetching data at that time react render the component and you got error as this.state.data is still {} so this.state.data.recipes[0] is yet not defined as fetch request is not completed (it take some time). To resolve it you have to return on 2 conditions.
when fetch is running (Loading)
2) when fetch is completed
render() {
if(!this.state.data.recipes){
// if the fetch request is still not completed
return (
<div>
<h1>Loading .... </h1>
</div>
)
}
// run when fetch request is completed and this.state.data is now assigned some data
return (
<div className="App">
<h1>{this.state.data.count}</h1>
<p>{this.state.data.recipes[0].publisher}</p> // Why this doesn't work?
</div>
);
}
answer for your comment.
the error is can not read property 0 of undefined which means this.state.data.recipes is undefined and thus.state.data.recipes[0] is error right?.
But when you use this.state.data.count then you did not get error. As it will print undefined that is value of count (at that moment and you are not trying to print further like this.state.data.count.toString() if you do so it will through you error can not read property toString() of undefined).
and in then() when you use this.setState() it will update the state and react will re-render all affected components.
Related
I need help using the axios dependancy on react.
I'm fetching data from this endpoint: https://api.covid19api.com/summary
I setup a config for my API (config.jsx)
import axios from 'axios';
export default axios.create({
baseURL: `https://api.covid19api.com/summary`,
responseType: "json"
});
then i call it in my App, (a class component) as API
import API from './config.jsx';
and execute it within my ComponentDidMount()
componentDidMount() {
API.get().then(res => {
const countries = JSON.stringify(res.data.Countries);
//console.log(countries);
this.setState({ covid: countries });
console.log(`Etat du state: ${this.state.covid}`);
})
}
I get the data, store it in my state named 'covid', but when it comes to map over the results i get an error "TypeError Cannot read property 'map' of null" I think i have to convert the data into an array but i don't know how to do this .
render() {
return (
<div className="App">
<header className="App-header">
<h1>{this.state.appliname}</h1>
{this.state.covid.map(item => (
<div>{item.Country}</div>
))}
</header>
</div>
);
}
Here's the full script on codesandbox: https://codesandbox.io/s/intelligent-faraday-ykewv?file=/src/App.js
Thanks
There are several things you need to consider:
Always handle errors in promises. Sometimes you may face API failure, so you should handle the API request gets failed that we should do. So simply add a catch handler to your promise chain.
You should always handle first data initiation. In the first render of your page, there is no this.state.covid so you can't pass it to your view and map through it, so if you do this it will throw an error. To make this work you should add conditional rendering to your element.
Define your first state initiation correctly. Since you expect your this.state.covid to be an array, so you should define it as an array in the first place (this.state = {covid: []}).
Avoid passing strings to Array#map. When you try to convert your incoming items from API to JSON with JSON.stringify(res.data.Countries) it will make your data as JSON. Since JSON comes with string type it won't fit array helpers like Array#map, when you got your data there is no need to make JSON of the. If in any case, your incoming data was JSON itself you should parse it with JSON.parse().
Working demo:
Set init state default array
this.state = { covid: [], appliname: "CovidFacts" };
Set countries
const countries = res.data.Countries;
Follow all :
import React from "react";
import "./App.css";
import API from "./config.jsx";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
covid: [],
appliname: "CovidFacts"
};
}
componentDidMount() {
API.get().then(res => {
const countries = res.data.Countries;
this.setState({ covid: countries });
console.log(`Etat du state: ${this.state.covid}`);
});
}
render() {
return (
<div className="App">
<header className="App-header">
<h1>{this.state.appliname}</h1>
{this.state.covid.map(item => (
<div>{item.Country}</div>
))}
</header>
</div>
);
}
}
export default App;
I am trying to fetch from an api to and display the data im getting. I have tried using regular fetch with promises and axios and it doesnt seem to be doing anything. Im using the .map() method and I am getting the error map is not a function.
I have tried using fetch and axios to try and get the data from the api.
import React, { Component } from 'react';
class RecentNews extends Component {
state = {
recentArticles: [],
recentReports: [],
isLoaded: false,
}
componentDidMount(){
fetch(`https://spaceflightnewsapi.net/api/v1/articles?page=1`)
.then(response => response.json())
.then(json => this.setState(
{ recentArticles: json,
isLoaded: true,
}
))
}
render(){
let { recentArticles, isLoaded } = this.state
if (!isLoaded) {
return <h1>Loading.....</h1>
}
else {
return(
<div className="recentnews">
<div>
{ recentArticles.map(articles =>
<p>{articles.title}</p>
)
}
</div>
</div>
)
}
}
}
export default RecentNews
here is the error I'm getting
TypeError: recentArticles.map is not a function
▶ 17 stack frames were collapsed.
.map() is a function of Arrays in JavaScript - that API is returning an Object.
It would appear that what you want is the docs array inside that object, so try changing that line to:
{ recentArticles.docs.map(articles =>
The other keys in the object that is returned by that API relate to the pagination. You should use those to create pagination controls, for example, next page, previous page links etc.
The code displays users records from an array.
I also creates an updateCount() function which counts users on content display.
I can see the count results alerted and in the console.
Now I want to display the count results and to this effect, I initialize setState() method within the updateCount function.
updateCount = userId => {
...some code missing
this.setState({dataCount: count});
}
This causes infinite loop of the results due to re-rendering.
Is there anyway to get the setState out of the UpdateCount() function to enable me display the count results well or any better possible approach.
Here is the Code
import React, { Component, Fragment } from "react";
import { render } from "react-dom";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
userCount: []
};
}
componentDidMount() {
var userId = "100";
this.setState({
data: [
{
id: "100",
name: "Luke"
},
{
id: "200",
name: "Henry"
},
{
id: "300",
name: "Mark"
}
]
});
this.updateCount = this.updateCount.bind(this);
}
updateCount = userId => {
var count = 1;
alert("counting: " + count);
console.log("counting: " + count);
this.setState({ dataCount: count });
};
render() {
return (
<span>
<label>
(---{this.state.dataCount}--)
<ul>
{this.state.data.map((person, i) => {
if (person.id == 100) {
//this.updateCount(person.id);
return (
<div key={i}>
<div>
{person.id}: {person.name}
</div>
</div>
);
} else {
this.updateCount(person.id);
}
})}
</ul>{" "}
</label>{" "}
</span>
);
}
}
You are calling in your render() method this.updateCount(person.id) which do a setState. Therefore a re-rendering occurs, and then this.updateCount(person.id) is called again. And it goes on.. (infinite loop)
From ReactJS docs:
The render() function should be pure, meaning that it does not modify component state, it returns the same result each time it’s invoked, and it does not directly interact with the browser.
Since updateCount() is calling setState(), you are calling setState in render. You need to redesign your code code, possibly creating array in state and using map in render.
Using getDerivedStateFromProps might be a good idea, make sure you to use a conditional statement, else you might hit another infinite loop.
Relatively new to React, wrote a few components before (successfully), but this is the first time I'm getting something started from scratch. I use Rails with Cassandra to provide JSON responses, and everything on the back end side is working fine.
My data this.state.data looks like this:
0 {id: {n: 2.1751612473052575e+38}, email: "123#zeba.me", high_score: 73, shoe_size: 10.5, updated_at: "2018-11-06T01:23:36.611-08:00"}
1 {id: {n: 2.8024982600468778e+38}, email: "123#sotkamonlukio.fi", high_score: 13, shoe_size: 7.5, updated_at: "2018-11-06T01:24:55.791-08:00"}
2 {id: {n: 2.4227336868283995e+38}, email: "123#icloud.com", high_score: 99, shoe_size: 10.5, updated_at: "2018-11-06T01:24:07.858-08:00"}
And doing this.state.data[1] obviously returns
{id: {n: 2.8024982600468778e+38}, email: "123#sotkamonlukio.fi", high_score: 13, shoe_size: 7.5, updated_at: "2018-11-06T01:24:55.791-08:00"}
However, this.state.data[1].email throws this
TypeError: undefined is not an object (evaluating 'this.state.data[1].email')
What can I do to access email and the rest of the data?
full component code:
import React, { Component } from 'react';
export default class Table extends Component {
constructor() {
super();
this.state = {
data: [],
}
}
componentDidMount() {
fetch('http://localhost:3000/api/users')
.then(response => response.json())
.then(data => this.setState({ data }))
}
render() {
return(
<div className="table">
<h1>LOL</h1>
<p>{this.state.data[1].email}</p>
</div>
)
}
}
The problem is that, when your component starts the rendering, the this.state.data[1].email wasn't loaded already. Just check if the data was already loaded, like below:
render(){
if(this.state.data.length > 0)
return(
<div className="table">
<h1>LOL</h1>
<p>{this.state.data[1].email}</p>
</div>
)
else return <div/>
}
The other possible solution is to use the map function. It will only render something when the array it's filled. The code is below:
render(){
return(
<div className="table">
<h1>LOL</h1>
{ this.state.data.map(user => <p>{user.email}</p>) }
</div>
)
}
For each user in the data array, the component will render a tag <p> with the email. When there's nothing in the array, the component will render nothing.
its a common error the request didn't complete before the render method is called just add an if statement
render () {
const { data } = this.state
return (
<div className='table'>
<h1>LOL</h1>
{data.length > 0 && <p>{data[1].email}</p>}
</div>
)
}
This.state.data initially set to an empty array. So when your component first time renders it will be an empty array.
In componentDidMount you are making an api call and assigning api response to the state data. So this method gets called after first render
The reason you get that issue because you are accessing 1st index from this.state.data array which is obviously an empty array at the time of first render. So what you have to do is
Change
<p>{this.state.data[1].email}</p>
To
<p>{this.state.data.length > 0 && this.state.data[1].email}</p>
Or do iterate the array and render p elements
{this.state.data.length > 0 && this.state.data.map(d => (
<p key={d.id}>d.email}</p>
)}
If you don’t have unique id per object in array then use index as key
Im new to react. I am trying to pull data from an API, and then loop through it and display it.
Error : Cannot read property 'map' of undefined.
The API data is coming through, but it seems as if React is calling the looplistings before the data is stored into State.
constructor () {
super()
this.state = {
data:'',
}
}
componentWillMount(){
// Im using axios here to get the info, confirmed data coming in.
//Updating 'data' state to equal the response data from the api call.
}
loopListings = () => {
return this.state.data.hits.map((item, i) => {
return(<div className="item-container" key={i}>
<div className="item-image"></div>
<div className="item-details">tssss</div>
</div>)
})
}
loopListings = () => {
return this.state.data.hits.map((item, i) => {
return(
<div className="item-container" key={i}>
<div className="item-image"></div>
<div className="item-details">tssss</div>
</div>)
})
}
render () {
return (
<div>
{this.loopListings()}
</div>
)
}
The reason you are receiving this error is that your call to the API is happening asynchronously to the react lifecycle methods. By the time the API response returned and persisted into the state the render method has been called for the first time and failed due to the fact you were trying to access an attribute on a yet undefined object.
In order to solve this, you need to make sure that until the API response has been persisted into the state the render method will not try to access that part of the state in your render method or to make sure that if it does there is a valid default state in the constructor:
Solve this by changing your render to do something like this:
render () {
return (
<div>
{this.state.data &&
Array.isArray(this.state.data.hits)
&& this.loopListings()}
</div>
)
}
or initialize your constructor like so :
constructor () {
super()
this.state = {
data: {hits: []},
}
}
Remeber react is just javascript and its behavior is just the same.
You could check if desir data.hits exists inside state.
{this.state.data && Array.isArray(this.state.data.hits) ?
this.loopListings()
: null}
Also make sure that, after retrieving a data cal this.setState method like below.
this.setState({ data })