I am creating weather app with React and I have an api which provides me object
import React, { Component } from 'react';
import WeeklyWeather from './components/WeeklyWeather';
const api = '';
class App extends Component {
constructor() {
super();
this.state = {
weather: {}
}
}
componentDidMount() {
fetch(api)
.then(response => response.json())
.then(data => this.setState({ weather: data }));
}
render() {
return (
<div className="App">
<WeeklyWeather day={this.state.weather.daily.data} />
</div>
);
}
}
After fetching it, I store data as state.
Finally, I want to pass this.state.weather.daily.data as props to child component, but I received TypeError: Cannot read property 'data' of undefined
<WeeklyWeather day={this.state.weather.daily.data} />
It can be you get the error because before the async request has finished, there is no this.state.weather.daily initialized. Quick hack could be something like this:
{ this.state.weather.daily && <WeeklyWeather day={this.state.weather.daily.data} />}
This will make sure WeeklyWeather is only rendered when daily is initialized.
the first render this.state.weather isn't initialized yet
follow this thread
React state data null for the first time
class App extends Component {
constructor () {
super()
this.state = {
weather: {}
}
}
componentDidMount () {
fetch(proxy)
.then(response => response.json())
.then(data => this.setState({ weather: data }))
}
render () {
return (
<div className='App'>
{this.state.weather && (
<WeeklyWeather day={this.state.weather.daily.data} />
)}
</div>
)
}
}
You're passing the prop to the child before it exists.
componentDidMount() is called once the HTML is bound to the DOM, which means your render() has already run. But, of course, your render() refers to this.state.weather.daily.data which doesn't exist until after componentDidMount() completes.
All you have to do is check that the data is loaded before you attempt to use it.
<WeeklyWeather day={this.state.weather.daily && this.state.weather.daily.data} />
Related
I have React class component called SearchLocationForm.js which is a child of App.js. Inside of the SearchLocationForm.js I have this code below
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
error: null,
isLoaded: false,
coordinates: []
}
this.appid = this.props.appid;
}
handleSubmit(location) {
fetch(
`http://api.openweathermap.org/geo/1.0/direct?q=${location}&appid=${this.props.appid}`
)
.then(res => res.json())
.then(resp => this.setState({coordinates: [resp.lat, resp.lon]}))
.catch((error) => {
console.error("there has been an issue with te GeoCode API Call", error)
})
}
render() {
return (
<LocationInput className="searchLocationForm" handleSubmit={this.handleSubmit}/>
)
}
I am trying to figure out how I can use the setState() method to update the component state with the API response, and afterwards lift that up to the App.js component in order to make subsequent calls.
I have lookup many answers regarding this problem and have tried implementing many solutions but nothing seems to work as of now. What do you see going on within my code that seems to be causing the issue? Thanks!
If yiu want only the coordinate will update and other states will remains same you have to use spread operator like this way
handleSubmit(location) {
fetch(`http://api.openweathermap.org/geo/1.0/direct?q=${location}&appid=${this.props.appid}`)
.then(res => res.json())
.then(resp => this.setState({...thia.state, coordinates: [resp.lat, resp.lon]}))
.catch((error) => {
console.error("there has been an issue with te GeoCode API Call", error)
})
}
And if you want to lift the state up to the app.js, you have to define the state on app.js and set the state from this component. Read more about lifting state up from here
Give you the full example. Do it like this way.
class App extends Component {
state = {
error: null,
isLoaded: false,
coordinates: []
}
handleSubmit(location) {
fetch(`http://api.openweathermap.org/geo/1.0/direct?q=${location}&appid=${this.props.appid}`)
.then(res => res.json())
.then(resp => this.setState({...thia.state, coordinates: [resp.lat, resp.lon]}))
.catch((error) => {
console.error("there has been an issue with te GeoCode API Call", error)
})
}
render() {
return (
<div>
<Hello name={this.state.name} />
<p>
Start editing to see some magic happen :)
</p>
<SearchLocationForm state={this.state} handleSubmit={this.handleSubmit} />
</div>
);
}
}
class SearchLocationForm extends Component {
constructor(props) {
super(props);
}
render() {
return (
<LocationInput className="searchLocationForm" handleSubmit={this.props.handleSubmit}/>
)
}
}
import React, { Component } from "react";
import Pokecard from "./Pokecard";
import "./Pokedex.css";
class Pokedex extends Component {
static defaultProps = {
pokemon: [],
getData() {
for (let i = 1; i <= 40; i++) {
fetch(`https://pokeapi.co/api/v2/pokemon/${i}`)
.then((response) => response.json())
.then((data) => {
this.pokemon.push({
id: data.id,
namePoke: data.name,
type: data.types[0].type.name,
base_experience: data.base_experience,
});
});
}
},
};
render() {
this.props.getData();
return (
<div className="Pokedex">
<div className="Pokedex-grid">
{this.props.pokemon.map((p) => (
<Pokecard
id={p.id}
name={p.namePoke}
type={p.type}
exp={p.base_experience}
key={p.id}
/>
))}
</div>
</div>
);
}
}
export default Pokedex;
I am new to React and I don't understand why my for loop runs multiple times so I get the Pokecards twice or more.
Also, the whole Component Pokedex is not showing up when reloading the page. What do I do wrong?
#azium made a great comment. You are calling to get data in your render, which is setting state, and causing a re-render, which is calling getData again, which is fetching data again and then setting state again, and the cycle continues on and on indefinitely. Also, default props should only define properties default values, but in this case you don't need a getData default prop. All you need to do is call the getData method in your componentDidMount. And your method needs to store the data in state, and not do a direct property change (like you are doing). Here is an example:
import React, { Component } from "react";
import Pokecard from "./Pokecard";
import "./Pokedex.css";
class Pokedex extends Component {
static state = {
pokemon: []
};
componentDidMount() {
this.getData();
}
getData() {
for (let i = 1; i <= 40; i++) {
const pokemon = [...this.state.pokemon];
fetch(`https://pokeapi.co/api/v2/pokemon/${i}`)
.then((response) => response.json())
.then((data) => {
pokemon.push({
id: data.id,
namePoke: data.name,
type: data.types[0].type.name,
base_experience: data.base_experience,
});
});
this.setState({pokemon});
}
}
render() {
return (
<div className="Pokedex">
<div className="Pokedex-grid">
{this.state.pokemon.map((p) => (
<Pokecard
id={p.id}
name={p.namePoke}
type={p.type}
exp={p.base_experience}
key={p.id}
/>
))}
</div>
</div>
);
}
}
export default Pokedex;
import React, { Component } from 'react';
import {withProvider} from './TProvider'
import ThreeCardMap from './ThreeCardMap';
class Threecard extends Component {
constructor() {
super();
this.state = {
newlist: []
}
}
componentDidMount(){
this.props.getList()
this.setState({newlist: [this.props.list]})
}
// componentDidUpdate() {
// console.log(this.state.newlist);
// }
render() {
const MappedTarot = (this.state.newlist.map((list, i) => <ThreeCardMap key={i} name={list.name} meaningup={list.meaning_up} meaningdown={list.meaning_rev}/>);
return (
<div>
<h1>Three Card Reading</h1>
<div>{ MappedTarot }</div>
</div>
)
}
}
export default withProvider(Threecard);
Hi, I'm trying to create a page that takes data from a tarot card API (https://rws-cards-api.herokuapp.com/api/v1/cards/search?type=major). Unfortunately by the time the data comes in, my map function has already fired. I'm asking to see if there is a way to have the map function wait until the data hits before it fires. Thanks!
Edit: getList function in the Context:
getList = () => {
console.log('fired')
axios.get('https://vschool-cors.herokuapp.com?url=https://rws-cards-api.herokuapp.com/api/v1/cards/search?type=major').then(response =>{
this.setState({
list: response.data
})
}).catch(error => {
console.log(error);
})
}
this.props.getList() is an async function. You are setting the list right after that call which is not correct.
You need to set it in the getList promise then() block.
getList() is an async function and update data for the parent component. So, my solution is just watching the list from the parent component if they updated or not, through getDerivedStateFromProps
class Threecard extends Component {
constructor() {
super();
this.state = {
newlist: []
}
}
// Set props.list to this.state.newList and watch the change to update
static getDerivedStateFromProps(nextProps, prevState) {
return {
newlist: nextProps.list
}
}
componentDidMount(){
this.props.getList()
// Removed this.setState() from here.
}
render() {
const MappedTarot = (this.state.newlist.map((list, i) => <ThreeCardMap key={i} name={list.name} meaningup={list.meaning_up} meaningdown={list.meaning_rev}/>);
return (
<div>
<h1>Three Card Reading</h1>
<div>{ MappedTarot }</div>
</div>
)
}
}
export default withProvider(Threecard);
I am new at React and trying to learn it. I am getting data from API and I will use the data. It returns money rates based on 'USD'. I am gonna use that data for convert money but I am getting this error: Error here
I don't know what the problem is.
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor (props) {
super(props)
this.state = {data: 'false'};
}
componentDidMount(){
this.getData();
}
getData = () => {
fetch("https://openexchangerates.org/api/latest.json?app_id=88a3d2b24b174bf5bec485533a3bca88")
.then(response => {
if (response.ok) {
return response;
} else {
let errorMessage =
'${response.status(${response.statusText})',
error = new Error(errorMessage);
throw(error);
}
})
.then(response => response.json())
.then(json =>{
console.log(json);
this.setState({ data: json.data })
});
}
render() {
return (
<div className="App">
<header className="App-header">
<h1 className="App-title">React App</h1>
</header>
{
this.state.data &&
this.state.data.map( (item, key) =>
<div key={key}>
{item}
</div>
)}
</div>
);
}
}
export default App;
Thanks for your time.
Set your initial data property in the state to an empty array [].
A stringified 'false' evaluates to true which is why it tries to call the map function, but string doesn't have a map function.
constructor (props) {
super(props)
this.state = {data: []};
}
Working example here
Because this.state.data is not an array, it's a string.
Just use the below code and remove this.state.data && from render method.
constructor (props) {
super(props)
this.state = {
data: []
};
}
I am fairly new to React, and trying to work my way through how I should properly be loading data from my API for a single post.
I have read that I should be using "componentDidMount" to make my GET request to the API, but the request is not finished by the time the component renders. So my code below does not work, as I am recieving the error: "Cannot read property setState of undefined".
What I am doing wrong here? Should I be calling setState from somewhere else? My simple component is below - thanks.
import React from 'react';
import Header from './Header';
import axios from 'axios';
class SingleListing extends React.Component {
constructor(props) {
super(props);
this.state = {
listingData: {}
}
}
componentDidMount() {
// Get ID from URL
var URLsegments = this.props.location.pathname.slice(1).split('/');
// Load the listing data
axios.get('/api/listing/' + URLsegments[1])
.then(function(res){
let listingDataObject = res.data;
console.log(listingDataObject);
this.setState({
listingData: listingDataObject
});
})
.catch(function(err){
console.log(err);
});
}
render() {
console.log('helsdfdsfsdflssosso');
console.log(this.state.listingData);
return (
<div className="SingleListing">
<Header />
<div className="container">
<div>Property Address: {this.state.listingData.propertyAddress}</div>
This is a single listing
</div>
</div>
)
}
}
export default SingleListing;
You just need to change what you render depending on whether the data is loaded or not yet.
Also, you should use arrow functions when handling the axios response, otherwise this is not set correctly.
class SingleListing extends React.Component {
constructor(props) {
super(props);
this.state = {
listingData: null,
};
}
componentDidMount() {
// Get ID from URL
const URLsegments = this.props.location.pathname.slice(1).split('/');
// Load the listing data
axios
.get(`/api/listing/${URLsegments[1]}`)
.then(res => {
const listingDataObject = res.data;
console.log(listingDataObject);
this.setState({
listingData: listingDataObject,
});
})
.catch(err => {
console.log(err);
});
}
render() {
const isDataLoaded = this.state.listingData;
if (!isDataLoaded) {
return <div>Loading...</div>;
}
return (
<div className="SingleListing">
<Header />
<div className="container">
<div>Property Address: {this.state.listingData.propertyAddress}</div>
This is a single listing
</div>
</div>
);
}
}
export default SingleListing;
this is out of scope you need to include it. here is a solution using es2015 arrow functions =>
axios.get('/api/listing/' + URLsegments[1])
.then((res) => {
let listingDataObject = res.data;
console.log(listingDataObject);
this.setState({
listingData: listingDataObject
});
})
.catch((err) => {
console.log(err);
});