I am creating an app using the REST countries API and I make an API call on the first render using useEffect hook and populate my useState array with the data that I need. When I try accessing this data in the return statement for the component it reads undefined. I even tried adding a ternary operator to first check if the data is empty and if not output it but it still reads undefined but If I comment out the code and refresh the page the data become populated and can then uncomment the code for it to show and if I were to refresh the page again the data reads as undefined. I am extremely confused as to why this is happening so any help is much appreciated.
My js file:
const CountryInfo = () => {
const [data, setData] = useState([{}])
let countryName = window.location.pathname;
countryName = countryName.replace('/', '');
countryName = countryName.split(' ');
countryName = countryName.join(' ');
console.log(countryName)
useEffect(() => {
axios.get(`https://restcountries.com/v3.1/name/${countryName}`).then(response => {
console.log(response);
setData([{
name: countryName,
image: response.data[0].flags.png,
nativeName: response.data[0].name.nativeName.nld,
population: response.data[0].population,
region: response.data[0].region,
subRegion: response.data[0].subregion,
capital: response.data[0].capital[0],
borderCountries: response.data[0].borders,
topLevelDomain: response.data[0].tld[0],
currencies: response.data[0].currencies,
languages: response.data[0].languages,
}])
})
},[])
console.log(data)
return(
<div className='country-info-content'>
<Link to='/' className='info-button'>
<button className='button-info'>BACK</button>
</Link>
<div className='data-info'>
<img src={data[0].image} className='flag-img'/>
<div className='country-name'>
<span className='name'>{data[0].name}</span>
<div className='info-description'>
<div>
<div>
<span>Native Name:</span> {data[0].nativeName.common}
</div>
{/* {data?
<div>
<div>
<span className='title'>Native Name:</span> {data.nativeName.common}
</div>
<div>
<span className='title'>Population:</span> {data.population.toLocaleString(undefined)}
</div>
<div>
<span className='title'>Region:</span> {data.region}
</div>
<div>
<span className='title'>Sub Region:</span> {data.subRegion}
</div>
<div>
<span className='title'>Capital:</span> {data.capital}
</div>
</div>:null} */}
{/* {data ?
<div>
<span>Native Name: </span>{data[0].nativeName.common}
</div>: null
} */}
</div>
<div className='right-info'>
<div>
<span className='title'>Top Level Domain:</span> {data.topLevelDomain}
</div>
<div>
<span className='title'>Currency:</span> {data.topLevelDomain}
</div>
<div>
<span className='title'>Languages:</span> {data.topLevelDomain}
</div>
</div>
</div>
</div>
</div>
</div>
)
}
export default CountryInfo;
CodePudding user response:
You have a data mapping error. You are setting your Data incorrectly when trying to set the native name in your data array.
you have:
setData([
{
...
nativeName: response.data[0].name.nativeName.nld,
...
}
]);
and return
<div>
<span className='title'>Native Name:</span> {data.nativeName.common}
</div>
But the data structure returned by the api looks different, eg:
data: Array(12)
0: Object
name: Object
common: "Eswatini"
official: "Kingdom of Eswatini"
nativeName: Object
eng: Object
ssw: Object
Change it to this instead:
setData([
{
...
nativeName: response.data[0].name.common,
...
}
]);
and
<div>
<span>Native Name:</span> {data[0].nativeName}
</div>
CodePudding user response:
useEffect will trigger AFTER your page is rendered for the first time, that's why data is undefined the first time component renders.
You'll need to handle it and the ternary operator is a common practice:
return data ? <div>...</div> : null;
or
return data ? <div>...</div> : <div>Loading...</div>;
UPDATE:
The error message is triggered by reading common
property of undefined. The only place where you do this is
data[0].nativeName.common
This means that data[0]
exists, but nativeName
is undefined. nativeName
stores the value of response.data[0].name.nativeName.nld,
.
You should double check if data returned from the API has expected format and response.data[0].name.nativeName.nld
is set.
UPDATE2:
Like Zeno noticed in the comment, default value for data
is [{}]
, and that will always evaluate to true
in ternary operator.
Why not make the default undefined
until data is fetched?
Also make sure you refer to the results as data[0]
as this is where you store the response. Or simplify your code a bit by doing:
setData({
name: countryName,
image: response.data[0].flags.png,
nativeName: response.data[0].name.nativeName.nld,
population: response.data[0].population,
region: response.data[0].region,
subRegion: response.data[0].subregion,
capital: response.data[0].capital[0],
borderCountries: response.data[0].borders,
topLevelDomain: response.data[0].tld[0],
currencies: response.data[0].currencies,
languages: response.data[0].languages,
})
There's no need to store an array in there.