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.
useEffect(() => {
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)
} )
}, [])
First problem: I am not passing selectedCountry.capital to the query, since console.log("capital"+selectedCountry.capital) returns undefined.
If I hardcode the query, I get a weather response.
useEffect(() => {
axios
.get(
`http://api.weatherstack.com/current?access_key=b51dfd70b0b2ccf136a0d7352876661c&query=New York`
)
.then(res =>{
console.log(res.data)
console.log("capital"+selectedCountry.capital)
setWeather(res.data.current)
} )
}, [])
I also tried to pass it like this
useEffect(() => {
axios
.get(
`http://api.weatherstack.com/current?access_key=b51dfd70b0b2ccf136a0d7352876661c&query=New York`
)
.then(res =>{
console.log(res.data)
console.log("capital"+selectedCountry.capital)
setWeather(res.data.current)
} )
}, [selectedCountry.capital])
[selectedCountry.capital]) I can log the capital. But it is some weird way to do it. And I can't display the weather data, since It will pass the selectedCountry.capital only after I select the country. There should be another way.
How do I pass selectedCountry.capital to the weather query?
Full code:
code sandbox
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;
Edit:
I get the array data passing selectedCountry.capital, like this
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])
However, I can't acess to the array after it has been fetched, resulting on an error.
TypeError: Cannot read property 'temperature' of undefined
Code updated
The issue is here:
useEffect(() => {
}, []);
This useEffect will run on component load and at that time ${selectedCountry.capital} is blank.
To handle this issue try something like:
useEffect(() => {
if( selectedCountry.capital} !== '' )
{
// Make your axios call here
}
}, [selectedCountry.capital]);
selectedCountry.capital is dependency so this effect will run on component load and every time selectedCountry.capital will change and on first time when this effect run we have a conditional check so axios request will not trigger.
Related
i'm developing an ecommerce whit Nextjs and payments with Paypal.
This is my product component
const Product = () => {
const router = useRouter();
const { id, category } = router.query;
const [product, setProduct] = useState();
const [amount, setAmount] = useState(1);
useEffect(() => {
if (category) {
const foundProduct = products[category].find(
(element) => element.id == id
);
setProduct({ ...foundProduct, amount, total: foundProduct.price * amount });
}
}, [id, amount]);
return (
<>
{!product ? (
<Spinner />
) : (
<div className="product-wrapper">
<div className="product-image">
<Image src={product.image} />
</div>
<div className="product-info">
<h3>{product.title}</h3>
<p className="product-price">
{product.currency} {product.price}
</p>
<p className="product-description">
{product.description}
</p>
<div className="product-cart-container">
<div className="product-cart-handle">
<p onClick={() => amount > 1 && setAmount(amount - 1)}>-</p>
<span>{amount}</span>
<p onClick={() => setAmount(amount + 1)}>+</p>
</div>
<BuyButtton item={product} amount={amount} />
</div>
<div className="product-general">
<p>General information</p>
</div>
</div>
</div>
)}
</>
);
};
and this is my BuyButton component
const BuyButtton = ({ item }) => {
useEffect(() => {
console.log(item);
}, [item]);
return (
<div>
<PayPalScriptProvider
options={{
"client-id":"",
}}
>
<PayPalButtons
createOrder={async () => {
try {
const res = await axios({
url: "http://localhost:3000/api/payment",
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
data: JSON.stringify(item),
});
return res.data.id;
} catch (error) {
console.log(error);
}
}}
onApprove={(data, actions) => {
console.log(data);
actions.order.capture();
}}
style={{ layout: "horizontal", color: "blue" }}
/>
</PayPalScriptProvider>
</div>
);
};
So when i pass this props item to my BuyButton component works fine, the amount and total value updates correctly, the problem is when i do the axios call, it looks like the component stores the initial value of the item prop, so amount and value never changes, its always amount:1, value:item.value. Any ideas? Thanks
I tried storing the item prop in a state but it didin't work as i expected
The solution that solved my problem was adding forceReRender prop to PayPalButtons like this forceReRender={[item]}, so it re-render the button and gets the new amount value
In the Product component, you are passing item & amount to BuyButton
<BuyButtton item={product} amount={amount} />.
You need to add the amount to the BuyButton component as well.
const BuyButtton = ({ item, amount }) => {
and pass the amount in the request body in the axios call
data: JSON.stringify(item),
// need to add the amount data
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 have a Dashboard component that is fetching all city data from an API and store it in the cities state.
Now I want that when a city name is clicked a new page opens having all the props of the clicked city.
function Dashboard() {
const [cities, setcities] = useState(null);
useEffect(() => {
axios.get('http://localhost:2000/city/')
.then(res => {
console.log(res);
setcities(res.data);
});
}, []);
const handleClick = (e) => {
// Here I want to show a detail page of the clicked item //
// <DetailsPage city={e} />
}
return (
<div >
<div>Dashboard</div>
<ul className="list-group list-group-flush">
{cities !== null ?
cities.map(city => {
return (
<li className="list-group-item" key={city._id} onClick={() => handleClick(city)}>
{city.City}
</li>
);
}) :
null
}
</ul>
{console.log(cities)}
</div>
);
}
If you like to show the details under the selected city you can keep it in your component state and render it conditionally:
function Dashboard() {
const [selectedCity, setSelectedCity] = useState(null);
const [cities, setcities] = useState(null);
useEffect(() => {
axios.get('http://localhost:2000/city/')
.then(res => {
console.log(res);
setcities(res.data);
});
}, []);
const handleClick = (e) => {
setSelectedCity(e)
}
return (
<div >
<div>Dashboard</div>
<ul className="list-group list-group-flush">
{cities !== null ?
cities.map(city => {
return (
<li className="list-group-item" key={city._id} onClick={() => handleClick(city)}>
{city.City}
{selectedCity === city ? <DetailsPage city={city} /> : null}
</li>
);
}) :
null
}
</ul>
{console.log(cities)}
</div>
);
}
If you want to only show the selected city content (with probably a back button):
function Dashboard() {
const [selectedCity, setSelectedCity] = useState(null);
const [cities, setcities] = useState(null);
useEffect(() => {
axios.get('http://localhost:2000/city/')
.then(res => {
console.log(res);
setcities(res.data);
});
}, []);
const handleClick = (e) => {
setSelectedCity(e)
}
if (selectedCity) {
return <DetailsPage city={e} onBack={() => setSelectedCity(null)} />
}
return (
<div >
<div>Dashboard</div>
<ul className="list-group list-group-flush">
{cities !== null ?
cities.map(city => {
return (
<li className="list-group-item" key={city._id} onClick={() => handleClick(city)}>
{city.City}
</li>
);
}) :
null
}
</ul>
{console.log(cities)}
</div>
);
}
If you want a separate page with a different URL, it will be more complex than this.
You need to use a router like react-router.
const handleClick = (e) => {
history.push("/city", { id: e.id });
}
You have to read the data on both pages. So you may need to put your cities and the selected city values in a React Context so that you can use it on the details page. Alternatively, you can fetch the data on the parent component and move these states to it, so that you can pass the values to both pages.
If you fetch data on the Dashboard page, you should also handle the scenario in which a user refreshes the details page or enters the URL directly. You may need a different API to fetch a city by its ID. Alternatively, you can simply redirect to the Dashboard page if you are on the details page and you don't have the required data.
I've created my backend and it works. I tested different Axios requests in order to create a form.
In my React front project, I created a POST axios request, I console.log(response.data) and I got an object with the id, the title and questions.
I am stuck because I don't know how I could display the data of the object in my front.
Here is my front React code:
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
const NewForm = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
if (data.length === 0) {
const response = await axios.post(
"https://back-formnest-lereacteur.herokuapp.com/form/create",
{
title: "Your event",
}
);
console.log(response.data);
setData(response.data);
}
};
fetchData();
}, [data]);
return (
I am completely stuck here to display the data of my backend in my front
This is my backend code:
const express = require("express");
const router = express.Router();
const Form = require("../models/Form");
router.post("/form/create", async (req, res) => {
try {
if (req.fields.title) {
const newForm = new Form({
title: req.fields.title,
});
await newForm.save();
return res.json(newForm);
} else {
return res.status(400).json({ error: "Missing parameters" });
}
} catch (e) {
return res.status(400).json({ error: e.message });
}
});
This is my console.log(response.data) I want to display in my front React page:
I edited my code and I got an error:
import React, { useState, useEffect } from "react";
/* import { Link } from "react-router-dom"; */
import axios from "axios";
const NewForm = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
if (data.length === 0) {
const response = await axios.post(
"https://back.herokuapp.com/form/create",
{
title: "Nouveau formulaire",
}
);
console.log(response.data);
setData(response.data);
}
};
fetchData();
}, [data]);
return (
<>
<div>My forms</div>
<div>
{data && (
<>
<p>{data.title}</p>
{data.questions.map((question, index) => (
<div> {question} </div>
))}
</>
)}
</div>
</>
);
};
export default NewForm;
Hi Guys,
I updated my code but I have still an error code (TypeError: Cannot read property 'length' of undefined)
<>
<div>My forms</div>
<div>
{data && (
<>
<p>{data.title}</p>
{data.questions.length &
data.questions.map((question, index) => {
return <p key={index}>{question}</p>;
})}
</>
)}
</div>
</>
I updated again my code, I succeeded only to display the title of my form but I did not succeed to display the data included in my question array. I have a "0" which appears instead of my data. Please help
return (
<>
<div>My forms </div>
<div>
{data && data.questions && (
<>
<div>{data.title} </div>
{data.questions.length &
data.questions.map((question, index) => {
return <p key={index}>{question}</p>;
})}
</>
)}
</div>
</>
I updated again, same error appears:
return (
<>
<div>My forms </div>
<div>
{data &&
data.questions &&
data.questions.length(
<>
<div>{data.title} </div>
{data.questions.map((question, index) => {
return <p key={index}>{question}</p>;
})}
</>
)}
</div>
you've done the hard part!
now just .map over the question array if you want to display them out?
<div>
{data.questions.map((question => (
<div> {question.title} </div>
))}
</div>
I've only done a simple example but of course you can display as much or as little as you want
of course anything in state you can render. so if you want to display title do:
{data.title} wherever pleases you
It looks like your backend responds with an object, so here is how you could go about it.
1) Change your initinal state to undefined like this.
const [data, setData] = useState([]);
to
const [data, setData] = useState(undefined);
Then you can use it in the display like this
return (
<div>
{data && (
<>
<p>{data._id}</p>
<p>{data.title}</p>
{data.question.length && data.question.map((question,idx) => {
// this is assuming that each question is just a string and not an object
return (<p key={idx}>{question}</p>)
})}
</>
)}
</div>
)
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.