How to access nested Obj in react? - reactjs

Why some attributes inside this weather Obj are accessable(first layer), but second layer cannot be accessed? See below picture for Obj.
with these code:
const Weather=(props)=>{
const{capital, api_key} = props
const [weather, setWeather] = useState('')
useEffect(() => {
axios
.get(`http://api.openweathermap.org/data/2.5/weather?q=${capital}&units=metric&appid=${api_key}`)
.then(response => {
setWeather(response.data)
})
}, [])
console.log('weather', weather)
return(
<div>
<div> temperature {weather.visibility} Celcius</div>
<img src='http://openweathermap.org/img/wn/03n#2x.png' alt="flag" width="120" height="100"></img>
<p>wind {weather.main.temp} m/s</p>
</div>
)
}
I can access e.g.,{weather.visibility}, but not {weather.main.temp}. Do you know why is it the case?

There is something wrong with your url, also on the first render weather.main.temp is undefined, because the api request is async code. The code below is working here:
const Weather=(props)=>{
const{capital, api_key} = props;
const [weather, setWeather] = useState('');
useEffect(() => {
axios
.get(`https://api.openweathermap.org/data/2.5/weather?q=${capital}&appid=${api_key}`)
.then(response => {
setWeather(response.data)
})
}, [])
console.log('weather', weather)
return(
<div>
<div> temperature {weather.visibility} Celcius</div>
<img src='http://openweathermap.org/img/wn/03n#2x.png' alt="flag" width="120" height="100"></img>
<p>wind {weather?.main?.temp} m/s</p>
</div>
)
}

Related

Updating displayed data on mouse enter

I would like to update text which is displayed inside a <div> element. I would love to do it when the cursor enters the <div> element.
Basically I'm fetching some data from the API and I display only one parameter (name). If a user enters the <div> with the name displayed I would like to show some details, i.e. description and price.
This is my code which I tried to complete my task.
import {useEffect, useState} from "react";
import requestOptionsGet from "../utilities/requestOptions";
import validateResponse from "../utilities/validators";
const Warehouse = () => {
const [items, setItems] = useState([]);
const [texts, setTexts] = useState([]);
const getItems = async () => {
const url = "http://localhost:8000/api/items/"
return await fetch(url, requestOptionsGet)
.then((response) => validateResponse(response, url))
.then((response) => response.json())
.then((data) => setItems(data))
};
useEffect(() => {
getItems();
}, []);
useEffect(() => {
setTexts(items.map((item) => (
{
id: item.id,
name: item.name,
description: item.description,
price: item.price,
currentDisplay: <h2>{item.name}</h2>,
})
))
}, [items]);
const displayName = (data) => {
console.log(
"displayName"
);
};
const displayDetails = (data) => {
const itemID = parseInt(data.currentTarget.getAttribute("data-item"));
const displayInfo = texts.find(text => text.id === itemID);
displayInfo.currentDisplay = <p>{displayInfo.description}</p>
setTexts(texts);
console.log(texts);
console.log(
"displayDetails"
);
return(
displayInfo.currentDisplay
);
};
return(
<div className="container">
<h1>Your warehouse.</h1>
<h2>All your items are listed here.</h2>
<hr />
{texts.map((text) => (
<button className="container-for-single-item" id={text.id} key={text.id}
onMouseEnter={displayDetails} onMouseLeave={displayName} data-item={text.id}>
{text.currentDisplay}
</button>
))}
</div>
);
}
export default Warehouse;
The functions work (everything is displayed in the console as it should be) and even the texts change. However the paragraph does not appear. How can I fix my code? Thanks!
Never modify state directly
const newTexts = texts.map(text => text.id === itemID ? { ...text, currentDisplay: <p>{text.description}</p> } : text);
setTexts(newTexts);

date-fns/formatDistanceToNow return 'Invalid time value'

I got error: 'Invalid time value' when using formatDistanceToNow from data-fns.
post.createdAt date format: 2022-09-10T18:12:10.072Z
Here is my code:
import formatDistanceToNow from 'date-fns/formatDistanceToNow'
export const SinglePost = () => {
const { id } = useParams()
const [post, setPost] = useState({})
useEffect(() => {
const fetchPost = async () => {
const response = await fetch(`/api/posts/${id}`)
const data = await response.json()
if (response.ok) {
setPost(data)
}
}
fetchPost()
}, [])
return (
<Flexbox >
<SinglePostStyle className='flex-item'>
<h1 className='post-title'>{post.title}</h1>
<p className="post-author">By: {post.author}</p>
// Invalid time value
<p className="post-date">{formatDistanceToNow(new Date(post.createdAt))}</p>
<img className='post-image' src={post.image} alt="" />
<p className='post-description'>{post.description}</p>
</SinglePostStyle>
<Sidebar className='flex-item' />
</Flexbox >
)
}
if you have any suggestion please let me know, thanks
Edited: I found the solution, i need to check if there is post.createdAt comming from request or not because first render post.createdAt is undefined.

how I test useEffect with isLoading state

I want to build test when the isLoading state change the component.
I know in the class component there is the way by do setState with enzyme, but I would like to know how I can do it here.
const Spacex = () => {
const [open, setOpen] = useState(false);
const [upComingLaunches, setUpComingLaunches] = useState([]);
const [Launchpad, setLaunchpad] = useState([])
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
let tempData;
SpaceXNextLaunche()
.then(data => {
setUpComingLaunches(data);
tempData = data;
return LaunchPad()
}).then(dataLaunch => {
const foundTheLaunch = dataLaunch.docs.filter((Launch, index) => {
return tempData.id === Launch.id
});
setLaunchpad(foundTheLaunch);
setIsLoading(false);
})
}, [])
if (isLoading) return <LoadingComp />
return (
<div>
<div className="upcoming-launches">
<h1 className={styles.title}>upcoming launche</h1>
<div className={styles.CountDownWarrper}>
{Object.keys(upComingLaunches).length > 0 ?
<Card className={styles.CountDownCard}>
<div className={styles.MissionName}>{upComingLaunches.name}</div>
<div className={styles.gridBadges}>
<div className={styles.CountDown}><CountDownClock upComingLaunches={upComingLaunches} /></div>
<div className={styles.badgeFlex}><img className={styles.badge} src={upComingLaunches.links["patch"]["small"]} alt="mission patch" /></div>
</div>
<GoogleMap
mapVisiblity={(e) => setOpen(!open)}
open={open}
placeName={Launchpad[0].launchpad.full_name} />
</Card>
: null}
</div>
</div>
</div>
)
}
export default Spacex;
The proper way to test functional components is to test the actual functions' behaviour, not their implementation. In your case that would be mocking the SpaceXLaunche() to return its data after some timeout, eg:
function SpaceXLauncheMock() {
return new Promise(resolve => {
setTimeout(resolve(data), 1500);
});
}
const SpaceXLaunche = jest.spyOn(SpaceXLaunche.prototype, 'SpaceXLaunche')
.mockImplementation(SpaceXLauncheMock);
then, you'd test your consequence of isLoading - the presence or absence of LoadingComp, initially, and again after the timeout (don't forget to put done as the test case's argument):
expect(component.contains(<LoadingComp />)).toBe(true);
setTimeout(() => {
expect(component.contains(<LoadingComp />)).toBe(false);
done();
}, 2000);

Fetch image based on text and display from API react

I've retrieved a list of categories using an API. Now I want to fetch images from an URL based on the categories. I tried using each category to fetch images from another API, but I'm not sure how to do it.
import React, { useEffect, useState } from 'react';
import './css/Category.css';
function Category() {
useEffect(() => {
fetchData();
getImage();
}, []);
const [categories, setCategories] = useState([]);
const [image, setImage] = useState('');
const fetchData = async () => {
const data = await fetch('https://opentdb.com/api_category.php')
const categories = await data.json();
console.log(categories.trivia_categories)
setCategories(categories.trivia_categories)
}
const getImage = async (name) => {
console.log(name)
const q = name.split(' ').join('+')
const img = await fetch(`https://pixabay.com/api/?key=apikey&q=${q}&image_type=photo`)
const image = await img.json();
console.log(image)
setImage(image.previewURL)
}
return (
<div className="categories">
Yesss
<div className="category-grid">
{categories.map(category => (
<div className="category">
{category.name}
<img src={getImage(category.name)} /> //do not know what to do here to fetch image of the respective category
</div>
))}
</div>
</div>
)
}
export default Category;
After changes suggested by Noah, I was able to show only one image.
const getImage = async (name) => {
const query = stringMan(name.name)
console.log(query)
const img = await fetch(`https://pixabay.com/api/?key=17160673-fd37d255ded620179ba954ce0&q=${query}&image_type=photo`)
const image = await img.json();
console.log(image)
setImage({ [name.name]: image.hits[0].largeImageURL })
}
return (
<div className="categories">
Yesss
<div className="category-grid">
{categories.map(category => (
<div className="category" key={category.id}>
{category.name}
<img key={category.id} src={image[category.name]} />
</div>
))}
</div>
</div>
)
There are a couple of changes that you can make here.
One issue that I see is that you have a single image variable, that's being re-used for every single category. So when you map over a list of categories (for example let's say we have categories: [history, science, and math]). The current code will call getImage three times, with history, science, and math as parameters.
However, there is only one state variable that is being written to. Which means the last execution of setImage is the only one that will be preserved.
So, you might want to change image from being the URL of a category image, to an object that has the shape:
{
history: [url],
science: [url],
math: [url]
}
The other change to make is that you are calling the getImage() function directly in the rendered output <img src={getImage(category.name)} />. Instead, this should simply use the value that was assigned to the image state: <img src={image} />.
To actually fetch the image, you can use the useEffect hook (https://reactjs.org/docs/hooks-effect.html) to react to changes to the categories variable. That might look something like:
useEffect(() => {
categories.forEach((c) => getImage(c));
}, [categories]);
The useEffect hook will invoke the function it is given, whenever the dependencies change. This will allow you to trigger the getImage function in response to changes to the categories.
There're lot of improvement that could be done as stated by #noah-callaway above/below but coming straight to the point you need to simply fix the URI creation logic to use encodeURIComponent like below:
import React, { useEffect, useState } from 'react';
function Category() {
useEffect(() => {
fetchData();
getImage();
}, []);
const [categories, setCategories] = useState([]);
const [image, setImage] = useState('');
const fetchData = async () => {
const data = await fetch('https://opentdb.com/api_category.php')
const categories = await data.json();
console.log(categories.trivia_categories)
setCategories(categories.trivia_categories)
}
const getImage = async (name) => {
return encodeURI(`https://pixabay.com/api/?key=apikey&q=${encodeURIComponent(name)}&image_type=photo`)
}
return (
<div className="categories">
Yesss
<div className="category-grid">
{categories.map(category => (
<div className="category">
{category.name}
<img src={getImage(category.name)} />
</div>
))}
</div>
</div>
)
}
don't have the api key so can't test but it'll give you something like
https://pixabay.com/api/?key=apikey&q=Entertainment%3A%20Comics&image_type=photo
good luck, hope it works.

Reactjs How is the weather state in <Weather /> component is undefined even though 'promise' is fullfilled?

I am trying to create a search filter for countries. I search a country and display their information and weather of country's capital using a weather api. I am fetching the data of a country using axios but the response.data is undefined and hence its cause error.
I know the code is async. So how do I fetch data from url before I setWeather(response.data) .
const Weather = ({capital}) => {
const [weather, setWeather] = useState([])
const key = 'mykey'
const url = `http://api.weatherstack.com/current?access_key=${key}&query=${capital}`
axios.get(url)
.then(response => {
console.log('promise fullfilled')
setWeather(response.data)
})
return(
<div>
<h1>Weather in {weather.location.name}</h1>
<h2>temperature: {weather.current.temperature} </h2>
<img src = {weather.current.weather_icons} />
<h2>wind: {weather.current.wind_speed} kph direction {weather.current.wind_dir}</h2>
</div>
)
}
const PrintLanguages = ({lang}) =>{
return(
lang.map(l => <li key={l}>{l}</li>)
)
}
const View = ({country}) =>{
const lang = country.languages.map(lang => lang.name)
return(
<div>
<h1>{country.name}</h1>
<p>capital {country.capital}</p>
<p>population {country.population}</p>
<h2>languages</h2>
<ul><PrintLanguages lang={lang}/></ul>
<img src={country.flag} alt="flag photo" height="100" width="100"/>
<Weather capital={country.capital}/>
</div>
)
}
I expected this result but instead I am getting this Type Error
Please guide me on how to fix this ??
You can use the effect hook here:
const Weather = ({capital}) => {
const [weather, setWeather] = useState({location:{}, current: {}});
const key = 'mykey'
const url = `http://api.weatherstack.com/current?access_key=${key}&query=${capital}`
useEffect(() => {
axios.get(url)
.then(response => {
console.log('promise fullfilled')
setWeather(response.data)
})
}, [capital]) // Fetch the data when capital changes
return(
<div>
<h1>Weather in {weather.location.name}</h1>
<h2>temperature: {weather.current.temperature} </h2>
<img src = {weather.current.weather_icons} />
<h2>wind: {weather.current.wind_speed} kph direction {weather.current.wind_dir}</h2>
</div>
)
}
This will call the api method after the component is mounted.
Also make sure your initial state structure is the same as the one for the rendered state. In your case you set it to an empty array but when rendering it expects an object.
One more way is to use a loading state, during which you can show a loading indicator before the data fetches:
const Weather = ({capital}) => {
const [weather, setWeather] = useState({location:{}, current: {}});
const [loading, setLoading] = useState(true);
const key = 'mykey'
const url = `http://api.weatherstack.com/current?access_key=${key}&query=${capital}`
useEffect(() => {
axios.get(url)
.then(response => {
console.log('promise fullfilled')
setLoading(false);
setWeather(response.data)
})
}, [])
return loading ? <p>Loading...</p> : (
<div>
<h1>Weather in {weather.location.name}</h1>
<h2>temperature: {weather.current.temperature} </h2>
<img src = {weather.current.weather_icons} />
<h2>wind: {weather.current.wind_speed} kph direction {weather.current.wind_dir}</h2>
</div>
)
}

Resources