So, in my code looks like setData works fine and gives the fetched data to my state. However it doesn't work at fist render. But if I cahange something in the code website will be rendered again and country name is gonna show up on the screen just how I wanted.
But this error keeps popping up:
Uncaught TypeError: Cannot read properties of undefined (reading 'common')
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
function Country() {
const { countryName } = useParams();
const [data, setData] = useState([]);
const [country, setCountry] = useState({});
useEffect(() => {
fetch('https://restcountries.com/v3.1/all')
.then(resp => resp.json())
.then(datas => setData(datas));
}, [])
useEffect(() => {
setCountry(data.find(a => a.name.common.toLowerCase() == countryName.replaceAll("-", " ")));
}, [data])
return (
<>
{
<h1>{country.name.common}</h1>
}
</>
)
}
export default Country
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
It means that country.name is undefined (and not an object with common property).
On initial render country is {} (an empty object).
So, country.name returns undefined.
And undefined does not have common property. Hence, the error.
So, a good rule of thumb before accessing a property would be to check if that property even exists.
<h1>{country?.name?.common}</h1>
Most of the time, you don't want to display tags if there is no data.
So, you would like to do something like this.
Markup after && will be rendered only if expression before it, is truthy value.
{country?.name?.common && (
<h1>{country.name.common}</h1>
)}
As country initial value is an empty object, the name property will be undefined, which causes this error, You just need to set an initial value for name too, as an empty object
const [country, setCountry] = useState({ name: {} });
Full example of code.
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
function Country() {
const { countryName } = useParams();
const [data, setData] = useState([]);
const [country, setCountry] = useState({ name: {} });
useEffect(() => {
fetch('https://restcountries.com/v3.1/all')
.then(resp => resp.json())
.then(datas => setData(datas));
}, [])
useEffect(() => {
setCountry(data.find(a => a.name.common.toLowerCase() == countryName.replaceAll("-", " ")));
}, [data])
return (
<>
{
<h1>{country.name.common}</h1>
}
</>
)
}
export default Country
Or you can optionally chain the name property.
<h1>{country.name?.common}</h1>
I opened a new tab in Chrome and paste your api https://restcountries.com/v3.1/all and from the network tab, the results have some strange objects which do not have "name": {"common"....}
You have to filter these objects out
Related
I'm doing an exercise to learn React in which I have set up a page with a list of clickable pokemon names which are linking to the pokemons specific detail page. Below is the code of the details page
import { useState, useEffect } from "react";
import axios from "axios";
import { useParams } from "react-router-dom";
export default function DetailsPage() {
const pokeName = useParams();
console.log(pokeName);
const [pokeList, setPokeList] = useState([]);
useEffect(() => {
const fetchData = async () => {
const response = await axios.get(
"https://pokeapi.co/api/v2/pokemon?limit=151"
);
console.log(response.data);
setPokeList(response.data.results);
};
fetchData();
}, []);
const specificPokemon = pokeList.find((pokemon) => {
return pokemon.name === pokeName.pokemon_name;
});
console.log(specificPokemon);
console.log(specificPokemon.name);
return <div><p>{specificPokemon.name}</p></div>;
}
This code has an error I fail to understand
The console.log(specificPokemon) works fine, but the console.log(specificPokemon.name) gives me the following error
Uncaught TypeError: Cannot read properties of undefined (reading 'name')
The correct code is the following, but I wonder why my method doesn't work
const [pokeList2, setPokeList2] = useState([]);
useEffect(() => {
const fetchData = async () => {
const response = await axios.get(
`https://pokeapi.co/api/v2/pokemon/${pokeName.pokemon_name}`
);
console.log(response.data);
setPokeList(response.data);
};
fetchData();
}, []);
console.log(pokeList);
Thank you
When the code runs first the pokeList is an empty array and it cannot find the property name. You should create a second state and do something like this
const pokeName = useParams();
const [pokeList, setPokeList] = useState([]);
const [specificPokemon, setSpecificPokemon] = useState({});
useEffect(() => {
const fetchData = async () => {
const response = await axios.get(
"https://pokeapi.co/api/v2/pokemon?limit=151"
);
setPokeList(response.data.results);
const selectedPokemon = response.data.results.find((pokemon) => {
return pokemon.name === pokeName.pokemon_name;
});
setSpecificPokemon(selectedPokemon)
};
fetchData();
}, [])
And don't forget to make the specificPokemon property optional like this specificPokemon?.name
When your component is mounted, pokeList is an empty array.
React will run the following block before the useEffect hook has finished running:
const specificPokemon = pokeList.find((pokemon) => {
return pokemon.name === pokeName.pokemon_name;
});
console.log(specificPokemon);
console.log(specificPokemon.name);
As long as your array is empty, specificPokemon will be undefined and calling specificPokemon.name will trigger your error.
Beware with console.log, its behavior is not always synchronous.
You might think specificPokemon is properly defined because console.log won't necessarily show undefined.
To verify this, use console.log(JSON.stringify(specificPokemon));.
I am fetching an object from api using axios.get("url"). The object fetched successfully (in Animal state) but there is a component level state (imageState) which requires updation using setState with fetched data. Code:Component:
import React,{useEffect, useState} from 'react'
import axios from 'axios'
const AnimalDetail = ({match}) => {
const [Animal ,setAnimal ] = useState({})
const Id = parseInt(match.params.id)
const [imageState, setImageState] = useState ("");
useEffect(()=>{
const fetchAnimal = async () => {
const {data} = await axios.get(`/api/animals/${Id}`)
setAnimal(data)
}
fetchAnimal()
// setImageState(Animal.image[0]) // need to access first index of image object
},[])
useEffect(()=>{
setImageState(Object.values(Animal.image)[0]) // error cant convert undefined to object
}
return (
<>
<h2>imageState </h2> //undefined
<h2>{typeof(Animal.image)}</h2> //gives object
</>
)
}
export default AnimalDetail
Backend Api :
{"id":2,
"image":["/image.jpg","/image2.jpg"],
"price":60000,
"breed":"",
"isAvailable":true,
"weight":110,
}
How can i fetch the data and update the component level state periodically(after fetching)?
You can try following, maybe this can help you. Removed the second useEffect and updated the image state in the first useEffect.
And also I can see, you have declared const [imageState, setImageState] = useState (""); twice. You can remove the second one.
Also, make sure you handle the API error in useEffect otherwise this may break the application on API failure.
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const AnimalDetail = ({ match }) => {
const [Animal, setAnimal] = useState({});
const Id = parseInt(match.params.id);
const [imageState, setImageState] = useState('');
useEffect(() => {
const fetchAnimal = async () => {
const { data } = await axios.get(`/api/animals/${Id}`);
setAnimal(data);
setImageState(data.image[0]);
};
if (Id) {
fetchAnimal();
}
}, [Id]);
return (
<>
<h2>imageState </h2> //undefined
<h2>{typeof Animal.image}</h2> //gives object
</>
);
};
export default AnimalDetail;
your code has some error in the second useEffect.
you can use this one :
useEffect(() => {
if (Animal) setImageState(Object.values(Animal.image)[0]); // error cant convert undefined to object
}, [Animal]);
this is because the Animal should have value first.
and you are defining imageState two times in your code! the first one is enough.
I have a parent component called App. I want to send the data that i took from the api(it includes random questions and answers) to child component.In child componenet(QuestionGrid), when i want to take the first question inside the array that come from api, I face the error. i want to use console.log(items[0].question) to see the first question but it fires error.But when I use console.log(items) it allow me to see them. I also aware of taking the data after they loaded.I used also useEffect. Here is my parent component
import './App.css';
import React, { useState,useEffect} from 'react';
import QuestionGrid from './components/QuestionGrid';
function App() {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
useEffect(() => {
fetch("https://opentdb.com/api.php?amount=40&category=9&difficulty=medium&type=multiple")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result.results);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, [])
return (
<div className="App">
<QuestionGrid isLoaded={isLoaded} items={items}/>
</div>
);
}
export default App;
Here is my child component
import React, { useState, useEffect } from 'react';
export default function QuestionGrid({ isLoaded, items }) {
if(isLoaded){
console.log(items[0].question)
}
return isLoaded ?
<section className="cards">
</section> : <h1>Loading</h1>;
}
It will fire and error because the initial state of items is an empty array. And there is no indexes and object on the items state on the first render.
you can check if the the items is loaded by only checking its length.
return items.length > 0 ? <h1>your jsx component</h1> : <span>Loading...</span>
First thing, you should use the .catch() in fetch like:
fetch("https://opentdb.com/api.php?amount=40&category=9&difficulty=medium&type=multiple")
.then(res => res.json())
.then((result) => {
setIsLoaded(true);
setItems(result.results);
})
.catch(error => {
setIsLoaded(true);
setError(error);
)}
)
You are checking for isLoaded but not if there is any data. You are setting isLoaded(true) in both your result and also in error (which is not bad).
The error is caused because there is nothing in items[0]. To check for this you can call console.log(items?.[0].question) or you can make the check in your if-condition if(items.length > 0)
I've got a React component that makes a call to a backend API which returns an array of objects.
Essentially, I've got a useFetch.js file that fetches data and is then displayed in a Products.js file
My main issue is that when I simply log the data that is returned, no matter how many times I refresh my browser, the correct data is always logged. However, as soon as I include a map to render the data, it displays only for the first time, and then I get an error
Cannot read property 'map' of null
The breakdown in the simple scenario when I just log the data:
Products.js
import React from "react";
import useFetch from "./useFetch";
import DisplayTable from "./DisplayTable"
export default function Products(props) {
const [data, loading, error] = useFetch("http://localhost:8080/products");
console.log("data", data)
console.log("loading", loading)
console.log("error", error)
return (
<div>
</div>
)
}
useFetch.js
import { useState, useEffect } from "react";
export default function useFetch(url) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [data, setData] = useState(null);
async function asyncFetchAPI() {
setLoading(true)
fetch(url, {method: "GET"})
.then(response => response.json())
.then(data => setData(data))
.then(loading => setLoading(false))
.catch((error) => {
console.log('error: ' + error)
setError(true)
})
}
useEffect(() => {
asyncFetchAPI();
}, []);
return [data, loading, error];
}
The above works fine, and I can see the data in the console perfectly well. However, when I modify Products.js to include the map and display as follows
const displayTable = data.map(item => <DisplayTable key={item.id} item={item} />)
The new result is now:
import React from "react";
import useFetch from "./useFetch";
import DisplayTable from "./DisplayTable"
export default function Products(props) {
const [data, loading, error] = useFetch("http://localhost:8080/products");
console.log("data", data)
console.log("loading", loading)
console.log("error", error)
const displayTable = data.map(item => <DisplayTable key={item.id} item={item} />)
return (
<div>
</div>
)
}
At that point, I get
Cannot read property 'map' of null
For what it's worth, my DisplayTable.js function looks like:
import React from "react"
function DisplayTable(props) {
console.log(props)
return (
<h1>{props.item.categoryId}</h1>
)
}
export default DisplayTable
You can fix it by initializing your data state with empty array in your useFetch.js file
const [data, setData] = useState([]);
or in the other file i.e.
const [data = [], loading, error] = useFetch("http://localhost:8080/products");
The problem is you are requesting data in an asynchronous manner right, so the first time, it is returned as null, then you try to map over null.
e.g. Here your data is null initially
const [data, setData] = useState(null);
console.log sometimes may show you the current state of variable, so don't get deceived by that.
So before mapping you can put a condition something like:
data && data.map....
or use empty array as initial value
Hello so am trying to make a custom hook where i get my user object from database and using it however my console log shows the the object is undefined shortly after object appears and this effect other function relying on it they only capture the first state and gives an error how can i fix that here is my code for the hook:-
import { useState, useEffect } from 'react'
import { storage, auth, database } from '../api/auth';
export default function useUser() {
const [user, setUser] = useState()
const userId = auth.currentUser.uid;
useEffect(() => {
getCurrentUser();
}, [])
const getCurrentUser = () => {
database.ref("users/" + userId).orderByChild('name').once("value").then(snapshot => {
setUser(snapshot.val())
})
}
return user
}
First of all you can assign default value to userObject.
const [user, setUser] = useState(null);
After place where you use useUser hook you can make a check is it null or not.
const user = useUser();
if (!user) {
return <Text>Loading...</Text>;
}
return(
<Text>{user.name}</Text>
);