So, the request is returning the JSON file. But when in console it is saying 'Undefined' and I do not know why.
So the button when clicked will send the results from my request from the google Place API; which contains the place_id needed to make the call to the Place Details API to the Info component.
const OnButtonClick = (restaurant) => {
setRestaurant(restaurant)
setOpenPopup(true)
}
<button className="cardButton" onClick={() => OnButtonClick(restaurantData)}>
View Information
</button>
<InfoPopup open={openPopup} restaurant={restaurant} onClose={() => setOpenPopup(false)} />
So, this works the way I think it does (Sorry, I am new to React)
Here's the InfoPopup component
function InfoPopup({ open, onClose, restaurant }) {
const [restaurant1, setRestaurant1] = useState([])
let id = restaurant.place_id
let URL = `/maps/api/place/details/json?place_id=${id}&key=${process.env.REACT_APP_API_KEY}`
const fetchRestaurants1 = async () => {
const res1 = await axios.get(URL)
setRestaurant1(res1.data.results);
}
useEffect(() => {
fetchRestaurants1()
console.log(restaurant1) //This is getting 'Undefined'
}, [id]);
const navigate = useNavigate()
if (!open) {return null}
return ReactDOM.createPortal(
<>
<div>
{restaurant1?.map(restaurant => (
<div key={restaurant.place_id}> {restaurant.formatted_phone_number} </div>
))}
</div>
<div className="popup">
<div className="popup-inner">
<button className="close-btn" onClick={onClose}> Close </button>
<h1 className="title"> {restaurant.name} </h1>
<ul>
{/* <li className="service">
Status: {}
</li> */}
<li className="location">
Address: {restaurant.vicinity}
Phone Number:
</li>
<li className="cost">
Cost: {restaurant.price_level}
</li>
{/* <li className="food">
Food Type:
</li> */}
</ul>
<div className="links">
<Link className="writeButton" to="/write" state={{data: restaurant}}>
Write a review
</Link>
{/* <button className="writeButton" onClick={() => navigate("/write", {data:restaurant})}>
Write a review
</button> */}
<Link className="readButton" to="/read" state={{data: restaurant}}>
Read the reviews
</Link>
{/* <button className="readButton" onClick={() => navigate("/read")}>
Read the reviews
</button> */}
</div>
</div>
</div>
</>,
document.getElementById('portal')
)
}
I think the problem is on the first render, there's no ID being passed. But I do not know how to work around it. Any help would be appreciated.
Looking at this block of code:
const fetchRestaurants1 = async () => {
const res1 = await axios.get(URL)
setRestaurant1(res1.data.results);
}
useEffect(() => {
fetchRestaurants1()
console.log(restaurant1) //This is getting 'Undefined'
}, [id]);
You're awaiting the result of the GET call, which is good, because it allows you to set state in the next line after waiting for the response.
The problem: when you call fetchRestaurants1() in the useEffect(), you're not waiting for that function to execute, therefore, we jump straight to the next line and console.log() restaurant1, which is of course still blank.
The same issue arises with set state calls.
If you do:
const [value, setValue] = useState(null);
then sometime later:
setValue(5);
console.log(value);
The value posted to console will be null, because JS doesn't want to wait for the set state call to finish before moving onto the next line, console.log(value);
To fix this: make the useEffect callback async, and await the functions in which you're making your axios.get calls. Example:
const fetchSomeData = async () => {
const response = await axios.get(URL);
setData(response.data);
}
useEffect(async () => {
await fetchSomeData();
/* do some stuff */
}, []);
Of course, you still can't console.log after the set state call above.
If you want a generalizable way to log state changes without worrying about async behavior, you can add a simple useEffect:
useEffect(() => {
console.log(value);
}, [value]);
Where value is any state variable. Since value is in the dependency array, anytime it changes, the useEffect will fire and log the change.
Related
My intention is to get the weather data for the selected country, passing selectedCountry.capital to the query, so it is displayed the weather from current country capital when the data of a country is displayed.
The problem is my code tries to render the weather data before the weather array is fetched, resulting in an error.
TypeError: Cannot read property 'temperature' of undefined
I get the array data
useEffect(() => {
if( selectedCountry.capital !== '' )
{
axios
.get(
`http://api.weatherstack.com/current?access_key=b51dfd70b0b2ccf136a0d7352876661c&query=${selectedCountry.capital}`
)
.then(res =>{
console.log(res.data)
console.log("capital" +selectedCountry.capital)
setWeather(res.data.current)
} )
}
}, [selectedCountry.capital])
render it
<div>
<h4>Weather</h4>
<h5>temperature: {weather.temperature} Celisues</h5>
<img src={weather.weather_icons[0]} alt='' />
<h5>
wind: {weather.wind_degree} mph direction {weather.wind_dir}
</h5>
</div>
i
If I don't render the array, I get the weather data just fine to the console. Also, If I add the array render code when the array is already there, the weather data gets displayed propperly.
What is the best way to make the array render wait for the array to be fetched from the axios call?
Full code
import React, { useState, useEffect } from 'react'
import axios from 'axios'
//setCountries is a function for setting the country's state
const App = () => {
const [countries, setCountries] = useState([])
//Filter
const [searchFilter, setSearchFilter] = useState('')
//Update state with button
const [selectedCountry, setSelectedCountry] = useState('')
const [weather, setWeather] = useState('')
const hook = () => {
console.log('effect')
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => {
console.log('promise fulfilled')
setCountries(response.data)
})
}
useEffect(hook,[])
/* by default the effect is always run after the component has been rendered. In our case, however, we only want to execute the effect along with the first render.
The second parameter of useEffect is used to specify how often the effect is run. If the second parameter is an empty array [], then the effect is only run along with the first render of the component. */
console.log('render', countries.length, 'countries')
console.log(countries)
/* weather */
useEffect(() => {
if( selectedCountry.capital !== '' )
{
axios
.get(
`http://api.weatherstack.com/current?access_key=b51dfd70b0b2ccf136a0d7352876661c&query=${selectedCountry.capital}`
)
.then(res =>{
console.log(res.data)
console.log("capital" +selectedCountry.capital)
setWeather(res.data.current)
} )
}
}, [selectedCountry.capital])
//When button es clicked the state is set, and the state variable is used
const renderCountryDetails = () => {
return (
selectedCountry && (
<p key={selectedCountry.alpha2Code}>
<p> Capital: {selectedCountry.capital}.</p>
<p> Population:{" "}
{selectedCountry.population}</p>
<p>
<img src={selectedCountry.flag} style={{ width: '200px'}}/>
</p>
<h3>Languages</h3>
<p> {selectedCountry.languages.map(language => <li key={language.name}>{language.name}</li>)}
<div>
<h4>Weather</h4>
<h5>temperature: {weather.temperature} Celisues</h5>
<img src={weather.weather_icons[0]} alt='' />
<h5>
wind: {weather.wind_degree} mph direction {weather.wind_dir}
</h5>
</div>
</p>
</p>
)
);
};
const filteredCountries =
searchFilter.length === 1
? countries
: countries.filter(
(country) => country.name.toLowerCase().indexOf(searchFilter.toLowerCase()) > -1
)
//showCountries returns either a message or else the contents of filteredcountries array
const showCountries = () => {
if (filteredCountries.length > 10) {
return 'Too many matches, keep on typing'
}
if (filteredCountries.length > 0
&& filteredCountries.length<10
&& filteredCountries.length>1 )
{
return (
<div>
{filteredCountries.map((country) => (
<p key={country.alpha2Code}>
{country.name}
{
//Update stste when button is clicked, passing country as a prop to the state
//onClick state is updated, causing the page to refresh and executing renderCountryDetails
//that uses the set state (the country) to render the info.
<button onClick={
() => setSelectedCountry(country)}>
show
</button>
}
</p>
))}
<div>{renderCountryDetails()}</div>
<div>
<p></p>
</div>
</div>
);
}
if (filteredCountries.length === 1) {
return filteredCountries.map((country) =>
<p key={country.alpha2Code}>
<p>Capital: {country.capital}.
<p> Population: {country.population} </p>
<h3>languages</h3>
{country.languages.map(language => <li key={language.name}>{language.name}</li>)}
<p><img src={country.flag} style={{ width: '200px'}}/>
</p>
</p>
</p>
)
}
}
const searchHandler = (e) => {
//setSelectedCountry state is set to empty
setSelectedCountry("");
setSearchFilter(e.target.value)
}
return (
<div>
<div>
<h1>Countries</h1>
</div>
<div>
Type to find countries:
<input onChange={searchHandler} />
<div>
{showCountries()}
</div>
</div>
</div>
);
}
export default App;
Simply use Optional chaining here:
<h5>temperature: {weather?.temperature||""} Celisues</h5>
In this case if the temperature is undefined it wont complain and would render an empty string instead.
"" can be replaced with any default value u need to show like 0 or something else in your case while your data is being fetched from API.
More on Optional chaining here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
I'm trying to render actual data in child component, but data does not render. What is wrong?
Parent component
const UserPanelContainer = ({ currentUser }) => {
const [initUsersData, setinitUsersData] = useState(currentUser);
useEffect(() => {
console.log('useEffect')
setinitUsersData(()=>getnewData())
}, [setinitUsersData, currentUser])
const getnewData = () =>{
console.log('getnewData')
setinitUsersData(currentUser)
}
return (
<UserPanel currentUser={initUsersData} hanleOnClickOut={hanleOnClickOut} >{console.log('usepanContainerRender')}</UserPanel>
);
};
export default UserPanelContainer;
child
const UserPanel = ({ currentUser, hanleOnClickOut }) => {
console.log(currentUser);
return (
<div className="dropdown">
{console.log('userPanelRender')}
<button
className="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownMenuButton"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<img
className="avatar"
src={currentUser.photoURL}
alt="avatar"
/>
{currentUser.displayName}
</button>
<div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
<div className="dropdown-item">
Вошел как {currentUser.displayName}
</div>
<div className="dropdown-item" onClick={hanleOnClickOut}>
Выйти
</div>
</div>
</div>
);
};
export default UserPanel;
In console in child I can see correct actual data in props, but they are not rendered.
Actual data contains "currentUser" prop. But on Browser page i cant see data....
(if i delete currentUser from useEffect depencity i can see data from previus API call)
I see you are passing the setinitUsersData in the useEffect dependency array whereas you need to pass the actual state variable
try this,
useEffect(() => {
...
}, [initUsersData, currentUser])
instead of current,
useEffect(() => {
...
}, [setinitUsersData, currentUser])
I think by actual data you mean some api response.
Try this :-
useEffect(() => {
console.log('useEffect')
getnewData(currentUser)
}, [currentUser])
const getnewData = (currentUser) =>{
console.log('getnewData')
axios.get("/pathToData").then((res) => {
console.log(res);
setinitUsersData(res);
})
}
Replace parent component with the following code. You don't need to use useEffect as per the code you've posted. Since parent is already receiving currentUser and you have already updated state with that
const UserPanelContainer = ({ currentUser }) => {
const [initUsersData, setinitUsersData] = useState(currentUser);
return (<UserPanel
currentUser={initUsersData}
hanleOnClickOut={hanleOnClickOut}>{console.log('usepanContainerRender')}</UserPanel>
);
};
export default UserPanelContainer;
I'm working on a Tinder-like app and trying to remove the current card from the array and move on to the next when clicking either the like or dislike button. Simultaneously, I am trying to add the card to a new array (list of liked or disliked). Adding the object to new array seems to work (although there's a delay and the button needs clicked twice - which also needs to be sorted), but as soon as I try to remove it from the current array it all crashes.
I tried looking at this solution: Removing object from array using hooks (useState) but I only ever get "TypeError: Cannot read property 'target' of undefined" no matter what I try. What am I missing?
This is the code:
import React, { useState, useEffect } from 'react';
import { Card, Button, Container } from 'react-bootstrap';
const url = 'https://swiperish-app.com/cards';
const SwiperCard = () => {
const [cardData, setCardData] = useState([]);
const [likedItem, setLikedItem] = useState([]);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(cardData => setCardData(cardData))
});
const handleRemoveItem = (event) => {
const name = event.target.getAttribute("name")
setCardData(cardData.filter(item => item.id !==name));
};
const likedCards = (itemId, itemImg, ItemTitle) => {
let likedArr = [...likedItem];
setLikedItem(likedItem => likedItem.concat({itemId, itemImg, ItemTitle}))
handleRemoveItem();
console.log(likedArr);
};
return (
<div id="contentView">
{cardData.map((item, index) => {
return(
<Card key={index} className="cardContainer" name={item.id}>
<Container className="btnContainer">
<div className="btnWrapper">
<Button className="btn" onClick={() => console.log(item.id)}>DISLIKE</Button>
</div>
</Container>
<Container className="cardContentContainer">
<Card.Img style={{width: "18rem"}}
variant="top"
src={item.image}
fluid="true"
/>
<Card.Body style={{width: "18rem"}}>
<Card.Title className="cardTitle">{item.title.toUpperCase()}</Card.Title>
<Card.Subtitle className="cardText">{item.body}</Card.Subtitle>
</Card.Body>
</Container>
<Container className="btnContainer">
<div className="btnWrapper">
<Button className="btn" onClick={() => likedCards(item.id, item.image,item.title) }>LIKE</Button>
</div>
</Container>
</Card>
)
})}
</div>
);
};
export default SwiperCard;
You can move cards between two arrays with
const likedCards = (item) => {
setLikedItem([...likedItem, item]);
let filtered = cardData.filter((card) => card.itemId !== item.itemId);
setCardData(filtered);
};
I suggest you to add empty array as second parameter of useEffect,since you are using as componentDidMount.
As second suggestion you can setLoading true before fetch and setLoading false after to reduce errors in render.
You're calling handleRemoveItem with no arguments, but that function is doing something with an event parameter, so you're going to get a TypeError.
It seems like handleRemoveItem really only needs to know about the item ID to remove, so you can simplify to:
const removeCard = id => {
setCardData(cardData.filter(item => item.id !== id));
};
const handleLike = (itemId, itemImg, ItemTitle) => {
setLikedItem([...likedItem, {itemId, itemImg, ItemTitle}]);
removeCard(itemId);
};
I also noticed that you're sometimes logging a state variable immediately after calling the setting. That won't work. It's not until the next call to useState on the next render when you'll receive the value, so if you want to log changes to state, I'd log in your render function, not in an event handler.
The this keyword inside the vidsAsHtml mapping function keeps returning undefined.
I read this, and a couple other SO questions about this but their solutions did not solve the problem. I'm already using es6 syntax arrow function for the map, but I've also tried putting in this as a second argument, which didn't solve the issue. Curious if anyone knows why 'this' keyword keeps coming up as undefined here.
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const VideoGrid = (props) => {
const [videos, setResource] = useState([])
const fetchVideos = async (amount, category) => {
const response = await axios.get('https://pixabay.com/api/videos/', {
params: {
key: '123456679',
per_page: amount,
category: category
}
})
console.log(response)
const vidsAsHtml = response.data.hits.map(vid => {
return (
<div className={`${props.page}--grid-content-wrapper`} key={vid.picture_id}>
<div className={`${props.page}--grid-video`}>
<video
poster="https://i.imgur.com/Us5ckqm.jpg"
onMouseOver={this.play()}
onMouseOut={this.pause()}
src={`${vid.videos.tiny.url}#t=1`} >
</video>
</div>
<div className={`${props.page}--grid-avatar-placeholder`}></div>
<div className={`${props.page}--grid-title`}>{vid.tags}</div>
<div className={`${props.page}--grid-author`}>{vid.user}</div>
<div className={`${props.page}--grid-views`}>{vid.views}
<span className={`${props.page}--grid-date`}> • 6 days ago</span>
</div>
</div>
)
})
setResource(vidsAsHtml)
}
useEffect(() => {
fetchVideos(50, 'people')
}, [])
return (
<main className={`${props.page}--grid-background`}>
<nav className={`${props.page}--grid-nav`}>
<button
id='followButton'
className={`${props.page}--grid-nav-${props.titleOne}`}
>{props.titleOne}
</button>
<button
id='recommendedButton'
className={`${props.page}--grid-nav-${props.titleTwo}`}
>{props.titleTwo}
</button>
<button
id='subscriptionsButton'
className={`${props.page}--grid-nav-${props.titleThree}`}
>{props.titleThree}
</button>
<button className={`${props.page}--grid-nav-${props.titleFour}`}>{props.titleFour}</button>
<button className={`${props.page}--grid-nav-${props.titleFive}`}>{props.titleFive}</button>
<button className={`${props.page}--grid-nav-follow`}>FOLLOW</button>
</nav>
<hr className={`${props.page}--grid-hr-nav-grey`} />
<hr className={`${props.page}--grid-hr-nav-black`} />
<div className={`${props.page}--grid`} style={{marginTop: 'unset'}}>
{videos}
</div>
</main>
)
}
export default VideoGrid
Event handler props are expected to be passed a function. Currently you are trying to pass the return values of this.play() and this.pause() as event handlers, which wouldn't work anyway.
Also React doesn't make the element available to the event handler via this, but you can access it via event.target:
<video
poster="https://i.imgur.com/Us5ckqm.jpg"
onMouseOver={event => event.target.play()}
onMouseOut={event => event.target.pause()}
src={`${vid.videos.tiny.url}#t=1`} >
</video>
You can use ref for this,
let vidRef = React.createRef();
You should create function separately,
const playVideo = () => {
// You can use the play method as normal on your video ref
vidRef.current.play();
};
const pauseVideo = () => {
// Pause as well
vidRef.current.pause();
};
provide ref to video,
<video
ref = {vidRef} //Provide ref here
poster="https://i.imgur.com/Us5ckqm.jpg"
onMouseOver={() => playVideo()}
onMouseOut={() => pauseVideo()}
src={`${vid.videos.tiny.url}#t=1`} >
</video>
Demo
The following code shows a list of 10 users (list-view) and if you click on Details button of any of those users, it shows only that particular user (user-view).
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const UserList = ({ id, setID }) => {
const [resources, setResources] = useState([])
const fetchResource = async () => {
const response = await axios.get(
'https://api.randomuser.me/?results=10'
)
console.log(response.data.results)
setResources(response.data.results)
}
useEffect(() => {
fetchResource()
}, [])
return (
<ul className='card__wrapper'>
{resources.filter(user => (id) ? user.login.uuid === id : true)
.map(item => (
<li className='card' key={item.name.first}>
<div className='card__item'>
<img className='card__image' src={item.picture.large} alt={item.name.first} />
<h2 className='card__title'>{item.name.first} {item.name.last}</h2>
{
id
?
<button
className='card__cta'
onClick={() => setID(null)}
>
Back to overview
</button>
:
<button
className='card__cta'
onClick={() => setID(item.login.uuid)}
>
Details
</button>
}
</div>
</li>
))}
</ul>
)
}
export default UserList
While this is working fine, the code inside the return which builds both the list-view and also the user-view is a bit difficult to understand (at least for me) and also makes it hard for using different CSS classes for List- and User-view.
I'd like to simplify the code so that's easier to understand by splitting it to two different returns.
Basically, saying that if the condition is true, return the user-view otherwise the list-view
How can I do that?
I would put the rendering stuff into another function, and to make what is going to be clearer I would use two returns:
import React, { useState, useEffect } from "react";
import axios from "axios";
const UserList = ({ id, setID }) => {
const [resources, setResources] = useState([]);
const fetchResource = async () => {
const response = await axios.get("https://api.randomuser.me/?results=10");
console.log(response.data.results);
setResources(response.data.results);
};
useEffect(() => {
fetchResource();
}, []);
const renderItem = (item, isLoggedIn) => {
return (
<li className="card" key={item.name.first}>
<div className="card__item">
<img className="card__image" src={item.picture.large} alt={item.name.first} />
<h2 className="card__title">
{item.name.first} {item.name.last}
</h2>
{isLoggedIn ? (
<button className="card__cta" onClick={() => setID(null)}>
Back to overview
</button>
) : (
<button className="card__cta" onClick={() => setID(item.login.uuid)}>
Details
</button>
)}
</div>
</li>
);
};
const user = resources.find(user => user.login.uuid === id);
if (user) {
return <ul className="card__wrapper">{renderItem(user, true)}</ul>;
} else {
return <ul className="card__wrapper">{resources.map(user => renderItem(user, false))}</ul>;
}
};
export default UserList;
Looks like the question asked pertains to this React hooks - OnClick show only the clicked item
Please find my comment for the above post, as I guess this particular issue can be solved as mentioned in the comment! In case it doesn't fix, let me know.