I am new to React, JS, JSX.
It doesn't appear that setNewWeather is updating the weather state properly, as in, it's defined by the initial value, but then changes to undefined.
Because if it updates, it should cause the re-render; I have looked at a lot of posts about this, but they advise to like, wait on the async data operation, but it's my understanding that using the '.then' method does that inherently? Or it's a different issue involving the syntax of the setNewWeather, like it needs to use a function inside instead of just a string, to update state?
My code:
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const Header = ({ text }) => <h1>{text}</h1>
const Header3 = ({ text }) => <h3>{text}</h3>
const Image = ({ source, alttext }) => <img src={source} alt={alttext} />
const Button = ({ onClick, text }) => (<button onClick={onClick}>{text}</button>)
const ListItem = ({ item }) => <li>{item}</li>
const List = ({ title, stuff }) => {
return(
<>
< Header3 text={title} />
<ul>
{stuff.map((item, index) => < ListItem key={index} item={item} />)}
</ul>
</>)}
const Search = ({ text, value, onChange }) => {
return (
<>
{text}
<input value={value} onChange={onChange} />
</>)}
const CountryMany = ({ country, handleClick }) => {
return (
<>
<li>{country.name}</li>
< Button onClick={handleClick(country.name)} text='Show' />
</>)}
const CountryFound = ({ country, api_key, handleWeather, newWeather }) => {
const countryFound = country[0]
const params = {
access_key: api_key,
query: countryFound.capital
}
useEffect(() => {
axios.get('http://api.weatherstack.com/current', {params})
.then(response => {
console.log('RESPONSE', response.data)
handleWeather({ is: 'weather', data: response.data })
console.log(newWeather)
})},
[params, newWeather, handleWeather])
console.log('yo')
console.log(newWeather)
const languages = countryFound.languages.map(lang => lang.name)
return (
<>
< Header text={countryFound.name} />
<p>Capital: {countryFound.capital}</p>
<p>Population: {countryFound.population}</p>
< List title='Languages' stuff={languages} />
< Header3 text='Flag' />
< Image source={countryFound.flag} alttext='flag' />
< Header3 text='Weather' />
<ul>
<li>Temperature: {newWeather}</li>
<li> Image source= alttext=weather </li>
</ul></>)}
const Countries = (props) => {
console.log('COUNTRIES PROPS', props)
console.log('WEATHER', props.newWeather)
const countries = props.countries
const foundCountries = countries.filter(country =>
country.name.toLowerCase().includes(props.newSearch.toLowerCase()))
if (foundCountries.length > 10 ) {
return (<p>Too Many Matches, Keep Typing!</p>)}
if (foundCountries.length > 1) {
return (
<ul>
{foundCountries.map(country =>
< CountryMany key={country.population} country={country} handleClick={props.handleClick} />)}
</ul>)}
if (foundCountries.length === 1) {
return (
<>
<CountryFound api_key={props.a_k1} country={foundCountries}
handleWeather={props.handleWeather} weather={props.newWeather} />
</>)}
return (<></>)}
const App = () => {
const api_key = process.env.REACT_APP_API_KEY
const [ countries, setCountries ] = useState([])
const [ newSearch, setNewSearch ] = useState('')
const [ newWeather, setWeather ] = useState({ is: 'no ewather' })
const handleWeather = ( is, data ) => () => {
setWeather( is, data )
console.log('HEY HANDLEWEATHER', newWeather)}
useEffect(() => {
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => {
setCountries(response.data)
})}, [])
const handleClick = (value) => () => {
setNewSearch(value)}
const handleSearch = (event) => {
setNewSearch(event.target.value)}
return (
<div>
< Search text='Find A Country: ' value={newSearch} onChange={handleSearch}/>
< Countries countries={countries}
a_k1={api_key}
handleWeather={handleWeather}
handleClick={handleClick}
newSearch={newSearch}
newWeather={newWeather}
/>
</div>)}
export default App
/*
const Weather = ({ weather }) => {
return (
<>
< Header3 text='Weather' />
<ul>
<li>Temperature: weather.temperature</li>
<li> Image source= alttext=weather </li>
</ul>
</>)}
*/
Thanks!
Edit: State is updating, but only by forming an infinite loop:
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const Header = ({ text }) => <h1>{text}</h1>
const Header3 = ({ text }) => <h3>{text}</h3>
const Image = ({ source, alttext }) => <img src={source} alt={alttext} />
const Button = ({ onClick, text }) => (<button onClick={onClick}>{text}</button>)
const ListItem = ({ item }) => <li>{item}</li>
const List = ({ title, stuff }) => {
return(
<>
< Header3 text={title} />
<ul>
{stuff.map((item, index) => < ListItem key={index} item={item} />)}
</ul>
</>)}
const Search = ({ text, value, onChange }) => {
return (
<>
{text}
<input value={value} onChange={onChange} />
</>)}
const CountryMany = ({ country, handleClick }) => {
return (
<>
<li>{country.name}</li>
< Button onClick={handleClick(country.name)} text='Show' />
</>)}
const CountryFound = ({ countryFound, api_key, handleWeather, newWeather }) => {
const params = { access_key: api_key, query: countryFound.capital }
useEffect(() => {
axios.get('http://api.weatherstack.com/current', {params})
.then(response => {
console.log('RESPONSE', response.data)
handleWeather(response.data)
})})
const languages = countryFound.languages.map(lang => lang.name)
if (newWeather.length > 0 ){
return (
<>
< Header text={countryFound.name} />
<p>Capital: {countryFound.capital}</p>
<p>Population: {countryFound.population}</p>
< List title='Languages' stuff={languages} />
< Header3 text='Flag' />
< Image source={countryFound.flag} alttext='flag' />
< Header3 text='Weather' />
<ul>
<li>Temperature/rendering {newWeather}</li>
<li> Image source= alttext=weather </li>
</ul></>)}
return (
<></>
)}
const Countries = (props) => {
console.log('COUNTRIES PROPS', props)
console.log('WEATHER', props.newWeather)
const foundCountries = props.countries.filter(country =>
country.name.toLowerCase().includes(props.newSearch.toLowerCase()))
if (foundCountries.length > 10 ) {
return (<p>Too Many Matches, Keep Typing!</p>)}
if (foundCountries.length > 1) {
return (
<ul>
{foundCountries.map(country =>
< CountryMany key={country.population} country={country} handleClick={props.handleClick} />)}
</ul>)}
if (foundCountries.length === 1) {
return (
<>
<CountryFound api_key={props.a_k1} countryFound={foundCountries[0]}
handleWeather={props.handleWeather} newWeather={props.newWeather} />
</>)}
return (<></>)}
const App = () => {
const api_key = process.env.REACT_APP_API_KEY
const [ countries, setCountries ] = useState([])
const [ newSearch, setNewSearch ] = useState('af')
const [ newWeather, setWeather ] = useState([])
const handleClick = (value) => () => {
setNewSearch(value)}
const handleSearch = (event) => {
setNewSearch(event.target.value)}
useEffect(() => {
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => {
setCountries(response.data)
})}, [])
return (
<div>
< Search text='Find A Country: ' value={newSearch} onChange={handleSearch}/>
< Countries countries={countries}
a_k1={api_key}
handleWeather={setWeather}
handleClick={handleClick}
newSearch={newSearch}
newWeather={newWeather}
/>
</div>)}
export default App
Edit Final: Solved!
The marked solution below solved the inital issue, yet generated an infinite loop. I have rectified the whole thing, although I do not quite understand yet how it has all changed.
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const Header = ({ text }) => <h1>{text}</h1>
const Header3 = ({ text }) => <h3>{text}</h3>
const Image = ({ source, alttext }) => <img src={source} alt={alttext} />
const Button = ({ onClick, text }) => (<button onClick={onClick}>{text}</button>)
const ListItem = ({ item }) => <li>{item}</li>
const List = ({ title, stuff }) => {
return(
<>
< Header3 text={title} />
<ul>
{stuff.map((item, index) => < ListItem key={index} item={item} />)}
</ul>
</>)}
const Search = ({ text, value, onChange }) => {
return (
<>
{text}
<input value={value} onChange={onChange} />
</>)}
const CountryMany = ({ country, handleClick }) => {
return (
<>
<li>{country.name}</li>
< Button onClick={handleClick(country.name)} text='Show' />
</>)}
const CountryFound = ({ countryFound, api_key, handleWeather, newWeather }) => {
useEffect(() => {
axios.get(`https://api.weatherbit.io/v2.0/current?city=${countryFound.capital}&key=${api_key}`)
.then(response => {
handleWeather(response.data.data[0])
})})
const languages = countryFound.languages.map(lang => lang.name)
if (newWeather > '' ) {
const capital = countryFound.capital
const weatherTitle = `Weather in: ${capital}`
const weatherImage = `https://www.weatherbit.io/static/img/icons/${newWeather.weather.icon}.png`
return (
<>
< Header text={countryFound.name} />
<p>Capital: {capital}</p>
<p>Population: {countryFound.population}</p>
< List title='Languages' stuff={languages} />
< Header3 text='Flag' />
< Image source={countryFound.flag} alttext='flag' />
< Header3 text={weatherTitle} />
< Image source={weatherImage} alttext='weather' />
<ul>
<li>Temperature: {newWeather.temp} degrees Celsius</li>
<li>Wind: {newWeather.wind_spd} mph towards {newWeather.wind_cdir}</li>
</ul></>)}
return (<><p>Loading...</p></>)}
const Countries = (props) => {
const foundCountries = props.countries.filter(country =>
country.name.toLowerCase().includes(props.newSearch.toLowerCase()))
if (foundCountries.length > 10 ) {
return (<p>Too Many Matches, Keep Typing!</p>)}
if (foundCountries.length > 1) {
return (
<ul>
{foundCountries.map(country =>
< CountryMany key={country.population}
country={country}
handleClick={props.handleClick} />)}
</ul>)}
if (foundCountries.length === 1) {
return (<>
<CountryFound api_key={props.a_k1} countryFound={foundCountries[0]}
handleWeather={props.handleWeather} newWeather={props.newWeather} />
</>)}
return (<></>)}
const App = () => {
const api_key = process.env.REACT_APP_API_KEY
const [ countries, setCountries ] = useState([])
const [ newSearch, setNewSearch ] = useState('af')
const [ newWeather, setWeather ] = useState('')
const handleClick = (value) => () => {
setNewSearch(value)}
const handleSearch = (event) => {
setNewSearch(event.target.value)}
useEffect(() => {
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => {
setCountries(response.data)
})}, [])
return (
<div>
< Search text='Find A Country: ' value={newSearch} onChange={handleSearch}/>
< Countries countries={countries}
a_k1={api_key}
handleWeather={setWeather}
handleClick={handleClick}
newSearch={newSearch}
newWeather={newWeather}
/>
</div>)}
export default App
Issues
handleWeather
is defined to take two argumentsBut when you call it you only pass a single argument
Additionally, react state updates are asynchronous and batched processed between render cycles, so trying to console log state right after the update is enqueued will only log the current state.
Solution
You should settle on either accepting the two arguments and creating the object you want in state, or consistently pass it the already-created object you want to store. The following will use the latter.
Note: At this point
handleWeather
is simply just proxying thenewWeather
object, so minor optimizations could be to not proxy since the function signatures match, i.e.const handleWeather = setWeather
, or just directly passsetWeather
as the callback.Use an effect to log the updated
newWeather
, usenewWeather
as the dependency.