Pulling information from OpenWeatherMap API when state (country) changes - javascript

Currently, I am only able to pull information when the name of the country is already loaded, I add the code, save it, then it's updated on localhost. But when I type in another country I get the error of Uncaught TypeError: Cannot read properties of undefined (reading 'temp'). For example, my code {weather.main.temp} works but only when the state = "france" is already set before.
My sub component:
const WeatherInfo = ({ country }) => {
const [weather, setWeather] = useState({});
useEffect(() => {
axios
.get(`https://api.openweathermap.org/data/2.5/weather?q=${country.capital}&appid=${api_key}&units=imperial`)
.then(response => {
console.log(response.data)
setWeather(response.data)
})
}, [country])
return (
<div>
<h3>Weather in {country.capital}</h3>
<p><b>Temperature</b>{weather.main.temp}</p>
</div>
)
}

weather is initialized as an empty object when WeatherInfo is created. Therefore weather.main is undefined and you get the error when you try to get the attribute temp from undefined.
You can resolve this by having some conditional chaining or some ternary operators depending on what you want displayed when weather is an empty object.
return (
<div>
<h3>Weather in {country.capital}</h3>
<p><b>Temperature</b>{weather.main?.temp}</p>
</div>
)

Related

Loop through const in React JSX return [duplicate]

This is based on exercise 2.14 of this course https://fullstackopen.com/en/part2/getting_data_from_server.
The user can select a country, then the weather information for that country's capital will be dislpayed. My code gives me error Cannot read property 'temperature' of undefined
const Weather = ({ city }) => {
const [weatherDetails, setWeatherDetails] = useState([])
useEffect(() => {
axios.get('http://api.weatherstack.com/current', {
params: {
access_key: process.env.REACT_APP_WEATHER_KEY,
query: city
}
}).then(
(response) => {
setWeatherDetails(response.data)
}
)
}, [city])
console.log('weather', weatherDetails);
return (
<div>
<h3>Weather in {city} </h3>
{weatherDetails.current.temperature}
</div>
)}
Basically, the line
{weatherDetails.current.temperature}
makes my code crash. When I do remove that line, I am able to see the response thanks to the console.log, but there are two consecutive logs
weather []
weather {request: {…}, location: {…}, current: {…}}
I figured that my code happens in between these two, and it tries to access the data before it has even arrived, but I don't know what to do to fix this.
Also, I don't know what the argument [city] of useEffect() does, so it'd be great if someone can explain to me what it does.
Edit: Solved!
Set weatherDetail's initial state to null and did some conditional rendering
if (weatherDetails) {
return (
<div>
<h3>Weather in {capital}</h3>
{weatherDetails.current.temperature} Celsius
</div>
)
} else {
return (
<div>
Loading Weather...
</div>
)
}
weatherDetails is an empty array, initially, so there is no current property to read from.
Use some conditional rendering. Use initial null state and then check that it is truthy to access the rest of the object when it is updated.
const Weather = ({ city }) => {
const [weatherDetails, setWeatherDetails] = useState(null) // <-- use null initial state
useEffect(() => {
axios.get('http://api.weatherstack.com/current', {
params: {
access_key: process.env.REACT_APP_WEATHER_KEY,
query: city
}
}).then(
(response) => {
setWeatherDetails(response.data)
}
)
}, [city])
console.log('weather', weatherDetails);
return (
<div>
<h3>Weather in {capital} </h3>
{weatherDetails && weatherDetails.current.temperature} // check that weatherDetails exists before accessing properties.
</div>
)}
What does the argument [city] of useEffect do?
This is the hook's dependency array. Hooks run on each render cycle, and if any values in the dependency array have updated it triggers the hook's callback, in this case, the effect to get weather data when the city prop updates.
useEffect
By default, effects run after every completed render, but you can
choose to fire them only when certain values have changed.

How do I map data from state to component prop?

To start with I'm a beginner. Any help would be appreciated.
So I'm getting my data from mongoDB atlas using node+express API. I'm successfull at getting the array to show up in console log using following code.
const [product, setProduct] = useState();
const url = "http://localhost:5000/api/items";
useEffect(() => {
axios.get(url).then((res) => {
setProduct(res.data);
// setProduct(
// JSON.stringify({
// title: setProduct.title,
// price: setProduct.price,
// image: setProduct.image,
// details: setProduct.details,
// })
// );
})
}, [url])
console.log(product)
The console log displays the array properly as collection named 'items' with content of arrays. As you can see I tried to stringify the response as the response returns JSON but again I didn't know how to map Following is the code where I tried to map the contents like id, name etc as props to component.
<div>
{product.map((product) => {
<Product name={product.title} />
})}
</div>
When I do this I get error that the map is not a function. I don't know what I'm doing wrong here. I know I'm supposed to use redux or reducer/context here but I want to get this to work before updating it with those.
[![Response from res.data][1]][1]
[1]: https://i.stack.imgur.com/auxvl.png
you didnt get yours products.
As we can see from screenshot
res.data equal to object with one property items:
res.data= {items: []}
and we need to take/access to these items
use this: setProducts(res?.data?.items || [])
const [products, setProducts] = useState();
useEffect(() => {
axios.get(url).then((res) => {
setProducts(res?.data?.items || []);
})
}, [url])
<div>
{products?.map((product) => {
<Product name={product.title} />
})}
</div>
On the first render the value for types will be undefined ( on sync code execution ), try using it as
<div>
{product?.map((product) => {
<Product name={product.name} />
})}
</div>
? will make sure to run map once value for types is there ( also make sure it is mappable ( is an array ))

In React, how can I make a single api call, set the result to a state variable, and display it?

I am programming a react application where I need to make a single async api call, save the result to a state variable and display the result. I am using an axios instance where the response of the api call is a nested object. For example:
{
kanji:
{character: "", stroke:""},
quote: "string"
}
So far I have the following code where I am able to console.log the homeData object successfully. However I get the error: 'TypeError: homeData is undefined'.
const Home = () =>{
const [homeData, setHomeData] = useState({})
const getHomeData = async()=>{
instance.get("/data")
.then(res =>{
setHomeData(res.data)
})
.catch(error =>{
console.log(error)
})
}
useEffect(()=>{
getHomeData()
}, [])
console.log(homeData)
return(
<>
<p>{homeData}<p>
</>
)
}
This seems like a promise issue but I am not sure how to fix it. Thank you for your help in advance.
This is not a promise issue, it has to due with the order in which the lifecycle methods run. useEffect runs after the component renders. This means when the component first renders, the homeData is an empty object. homeData will be present in the second render. The following code first checks if homeData exists, then if it exists, it displays the kanji character. Note also, you cant just display a raw object into the dom so you will have to access the homeData by property for it to display
const Home = () =>{
const [homeData, setHomeData] = useState({})
const getHomeData = async()=>{
instance.get("/data")
.then(res =>{
setHomeData(res.data)
})
.catch(error =>{
console.log(error)
})
}
useEffect(()=>{
getHomeData()
}, [])
console.log(homeData)
return(
<>
<p>{homeData && homeData.kanji.character}<p>
</>
)
}

'TypeError: Cannot read property of x of undefined' when passing data from parent to child component but others are working

Good day, I have a project that gets a response from an API then passes down the data from parent to child. The problem is, I can easily access the response at the top level but when I try to get in the inner parts of the API (in this case, the price={statistics.quotes.USD.price}) , I'm getting the TypeError: Cannot read property 'USD' of undefined error. I have tried console.logging the price to check if my path is correct and it is. Why could this be happening when I can access other data correctly?
Overview.js
import React, { useState, useEffect } from 'react';
import Statistics from './Statistics';
import axios from 'axios';
export default function Overview(props) {
const id = props.match.params.currency;
//some other states here
const [statistics, setStatistics] = useState({});
//some code
const fetchBasicData = async () => {
// Retrieves a coin's basic information
const apiCall = await axios.get('https://api.coinpaprika.com/v1/coins/' + id);
let basicData = await apiCall.data;
setCoin(basicData);
// Retrieves coin statistics
const fetchedData = await axios.get('https://api.coinpaprika.com/v1/tickers/' + id);
const coinStats = await fetchedData.data;
setStatistics(coinStats);
}
useEffect(function () {
if (Object.keys(coin).length === 0 && Object.keys(statistics).length === 0) {
fetchBasicData();
}
})
//some code
return (
<div>
//some other stuff
<Statistics
statistics={statistics}
lastUpdate={statistics.last_updated}
price={statistics.quotes.USD.price} // <----- this is where the error occurs
/>
</div>
);
}
Statistics.js
import React from 'react';
export default function Statistics(props) {
return (
<div>
<h1>Statistics</h1>
<p>Last updated: {props.lastUpdate}</p>
<p>Price: {props.price}</p>
<p>Market Rank: {props.marketRank}</p>
<h2>Supply</h2>
<p>Circulating supply: {props.circulatingSupply}</p>
<p>Max supply: {props.maxSupply}</p>
</div>
);
}
Hi it could be that some of rows in your data do not have quotes try doing following change it should be fixed by this
change
price={statistics.quotes.USD.price}
to
price={statistics?.quotes?.USD?.price}
? checks if given variable is present and if not return null and does not throw an error
As you are using the axios call, which is asynchronous and the data of that call is not available on initial render, but it will be available later on. So to handle this you have to conditional render (render only when certain condition met)
Try this:
price={statistics?.quotes?.USD?.price}
Or you can also use Object.hasOwnProperty('key') with ternary and do the conditional render.
Issue
Error: TypeError: Cannot read property 'USD' of undefined is saying that statistics.quotes is undefined.
There are 2 possible causes:
On the initial render you are accessing too deeply into your initial state.
On any subsequent render the statistics isn't updated to something you're expecting.
My guess is that your data fetching and state update is fine and it's just the initial render issue.
The initial statistics state is an empty object ({}), so accessing any property is fine. It's when you then go a nested level deeper into the structure that causes the issue.
<Statistics
statistics={statistics} // OK: statistics => {}
lastUpdate={statistics.last_updated} // OK: statistics.last_updated => undefined
price={statistics.quotes.USD.price} // Error: can't access USD of undefined statistics.quotes
/>
const statistics = {};
console.log(statistics); // {}
console.log(statistics.quotes); // undefined
console.log(statistics.qoutes.USD); // error!!
Solution
You can use Optional Chaining operator (?.) or guard clauses (null checks) to protect the "access X of undefined" errors.
<Statistics
statistics={statistics}
lastUpdate={statistics.last_updated}
price={statistics.quotes?.USD?.price}
/>
<Statistics
statistics={statistics}
lastUpdate={statistics.last_updated}
price={statistics.quotes && statistics.quotes.USD && statistics.quotes.USD.price}
/>
If there's even a remote chance that your statistics state can be updated to undefined then apply the same fix as above but just a level shallower, i.e. statistics?.quotes?.USD?.price.
Alternatively you can apply some Conditional Rendering of the Statistics component where the condition is the nested properties existing on the statistics state.
return (
<div>
//some other stuff
{statistics.last_updated && statistics.quotes && (
<Statistics
statistics={statistics}
lastUpdate={statistics.last_updated}
price={statistics.quotes.USD?.price}
/>
)}
</div>
);

Understanding useEffect behaviour

I have a component called StudentPage which shows the details of a student depending on the current student logged in currentStudent . The currentStudent object doesn't hold all the attributes of the Student model, so I fetch all the students with an axios request and get the student which matches the curresntStudent by id, and get all the attributes I need from it.
Now the problem is when I try to access the value of a student property, I get the error "TypeError: Cannot read property 'name' of undefined".
So I console.log- students, currentStudent, and student and noticed that when I try to access the currentStudent.name (commented below), the console.log-students outputs the list of students from my db, also noticed that the output of console.log-student object matches that of currentStudent. However, when I try to access student.name (code below), the console.log outputs shows an empty array for the students list causing the student object to be undefined.
Can anyone please help explain why I am noticing this behaviour, perhaps some concept of useEffect that I don't yet understand.
const StudentPage=({currentStudent, students, fetchStudents})=>{
useEffect(()=>{
fetchStudents();
},[]) //I tried making currentStudent a dependency [currentStudent] but same behaviour ocurs.
console.log(students);
console.log(currentStudent)
const student= students.filter(pupil=>{return(pupil.id===currentStudent.id)})[0]
console.log(student)
// return (<div>name: {currentStudent.name}</div>) this works normally but currentUser doesn't have all attributes i need
return(
<div>Name: {student.name}</div>
)
const mapStateToProps= state=>{
return {
students: state.students
}
}
export default(mapStateToProps,{fetchStudents})(StudentPage);
Here you can find an example on how to do what you want:
import React from "react";
import { useState, useEffect } from "react";
export function StudentPage({ studentId, fetchStudents }) {
const [student, setStudent] = useState();
useEffect(() => {
(async () => {
const students = await fetchStudents();
const currentStudent = students.find((student) => {
console.log(student);
return student.id === studentId;
});
setStudent(currentStudent);
})();
}, [studentId, fetchStudents]);
return (
<>
Student ID: {studentId}, Student name: {student && student.name}
</>
);
}
useEffect will be triggered whenever the dependency (studentId in this case) changes, including on first render. You should store you value in state (using useState) and have the necessary checks in place to see whether it is null or not.

Categories

Resources