React.js, pulling data from api and then looping through to display - reactjs

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 })

Related

Uncaught TypeError: Cannot read properties of undefined (reading 'map') Why this occurred after doing all the possible ways to solve

I'm using API key to iterate the data, I am getting this error again and again. I did all possible ways to solve it. I made array named articles to store all the data in it and then iterated it. It was run perfectly when I manually fetch data in it by an array but when I used API it started giving me this error.
import Newsitem from './Newsitem'
export class News extends Component {
articles = [];
constructor() {
super();
this.state = {
articles: this.articles,
loading: false
}
}
async componentDidMount() {
let url = `https://newsapi.org/v2/top-headlines?
country=in&category=business&apiKey=549bc58bb2fd419088ac863611e7339e`;
let data = await fetch(url);
let parsedData = await data.json()
console.log(parsedData);
this.setState({
articles: parsedData.articles
})
}
render() {
return (
<>
<div className='container my-3'>
<h1>News of the day - in Nutshell</h1>
<div className="row my-4">
{this.setState.articles.map((element) => {
return <div className="col-md-4 my-3" key={element.url}>
<Newsitem title={element.title ? element.title.slice(0, 45) : ""}
discription={element.description ? element.description.slice(0, 88) : ""} imgUrl=
{element.urlToImage} newsUrl={element.url} />
</div>;
})}
</div>
</div>
</>
)
}
}
export default News```
I tried all possible ways to fix this but didn't work anything of them.
Why it
Following the [React Component Article][1] "setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state. This is the primary method you use to update the user interface in response to event handlers and server responses."
Therefore, I believe you mean to do this.state.articles instead of this.setState.articles.

ReactJS - TypeError: Cannot read property '0' of undefined

I'm trying to make a weather app in order to learn some ReactJS.
I called the OpenWeatherMap API using this fetch in a componentDidMount method inside of my WeatherCard component.
constructor() {
super()
this.state = {
weatherData: {}
}
}
componentDidMount() {
fetch('//api.openweathermap.org/data/2.5/weather?q=Calgary,ca&APPID=XXX')
.then(response => response.json())
.then(data => {
this.setState({
weatherData: data
})
})
}
Here's a sample JSON output of the above call (given a valid API key):
I get this error message whenever I want to access a weather property:
TypeError: Cannot read property '0' of undefined
... and I access it like this:
this.state.weatherData.weather[0].main
Here is also my render method if it helps with the problem:
render() {
return (
<div>
{this.state.weatherData.weather[0].main}
</div>
)
}
Does anyone know what might be the problem I'm running into? Thanks a lot!
On the first render the request has not yet started, and therefore there is no data yet, you need to have a loading boolean or just check if the data is already loaded before trying to access it.
render() {
if (!this.state.weatherData.weather) {
return <span>Loading...</span>;
}
return (
<div>
{this.state.weatherData.weather[0].main}
</div>
)
}
When are you trying to access it?
Can you try this:
render() {
return (
<div>
{this.state.weatherData.weather ? this.state.weatherData.weather[0].main : null}
</div>
)
}
It's possible that the render() method is trying to access this.state.weatherData before the data finishes fetching. Try using an inline if to display only when the data is there:
<div>
{this.state.weatherData &&
<p>{this.state.weatherData.weather[0].main}</p>
}
</div>
You have to consider that this.state.weatherData.weather might be undefined or null.
Should try this :
{this.state.weatherData.weather ? this.state.weatherData?.weather[0].main : null}

Return response.data in function inside .then callback | Axios

Is it possible to return a function inside the .then callback?
I want to do something like this:
axios.post('url_api/endpoint')
.then(function(response){
renderAdmissionReponse(){
<div>Success response.data</div>
}
})
To later render it in a component like this: "
<div className="row spacer">
{this.renderAdmissionReponse()}
</div>
Is it possible to do it?
I can actually understand your need, however, your above code is wrong!
What you want is: after data are loaded successfully by using axios, the data will be rendered properly on page. If so, you can use the following way:
constructor(props) {
// ...
this.state = ({
admissionResponse: [],
// your other state variables here
})
}
renderAdmissionReponse = (data) => {
this.setState({
admissionResponse: data,
});
}
// ... You can put this inside `ComponentDidMount()`, then just call the function to setState with returned data
axios.post('url_api/endpoint')
.then((response) => {
this.renderAdmissionReponse(response.data);
})
// ...
render() {
return (
<div className="row spacer">
<div>{this.state.admissionResponse.authorizationId}</div>
<div>{this.state.admissionResponse.authorizationNum}</div>
{this.state.admissionResponse.lineItems.map((line) =>{
return(
<div>{line.id}</div>
<div>{line.lineItemNumber}</div>
)
})}
</div>
);
}
Please also check your response.data structure then retrieve the correct keys, the above code I just try to explain about correct react's way on how to load data asynchronously and render on page after that. Please post here some errors if any, thanks.

How do I return jsx from an outside function with callback?

I am trying to return jsx from outside my render method. It works when there is no api request, but as soon as I add the request and add a callback I can't return the jsx. How do I do this? renderLocations is being called in my render method.
renderLocations() {
this.Geo((coord) => {
return this.state.location.map((location, index) => {
return (
<div>
{coord}
</div>
);
})
})
}
You should not do it like this.
A. You are not returning anything from renderLocations
B. If you are waiting for some data that will arrive later. Initiate the call in componentWillMount lifecycle method and upon arrival update the state of the component using this.setState({coord: coord}) - this would run the render method again. In the render method wherever you are calling the renderLocations method pass the this.state.coord
And change the renderLocations method like this
renderLocations(coord) {
if (coord) {
return this.state.location.map((location, index) => {
return (
<div>
{coord}
</div>
);
})
}
}
Also, dont forget to initialize the state in getInitialState method or in the constructor.
In the example you provided, you are not returning anything from your method. I'd suggest you do something like this where you can add coord to state.
componentDidMount() {
this.Geo((coord) => this.setState({coord});
},
renderLocations() {
const {coord} = this.state
return this.state.location.map((location, index) => {
return (
<div>
{coord}
</div>
);
})
}

Why is my render function running before my componentWillMount

Whenever I try to fetch my results from componentWillMount the application hits render() first it comes out and then it hits componentWillMount fetches my results, sets the state and then hits render again.
componentWillMount= () => {
let team = gh.getRepo('Microsoft', 'vscode');
team.getContributors(function(err, members){
}).then(response => {
this.setState({
data: response.data
});
});
}
render() {
var ghList = this.state.data;
const names = ghList.map(name => { //when it runs the first time this map in invalid since ghList is null from my this.setState= {data:null}
})
return (
<div className="flip-container" onClick={this.onTileClick}>
<div className="flipper">
<div className="front">
<img className="circle" src={this.state.avatar_url} alt={this.state.login}/>
</div>
<div className="back">
</div>
</div>
</div>
)
If I understood correctly, it's the expected behavior. What is happening:
componentWillMount(): triggers an async request (getContributors).
render() first time, with this.state.data = undefined.
callback from getContributors is called (the response has come), and, then, setState is invoked: setState schedules a new rendering.
render() second time, with populated this.state.data
You'll have to handle your initial state rendering (how your component will render before the ajax returns: maybe some loading spinner..). So, in the render method, you can check if this.state.data is null/undefined and bypass your logic. Or, in constructor, set your data to an empty array:
constructor(props) {
super(props);
this.state = {
data: []
}
}
This way, at least ghList.map will not thrown an error.

Resources