Pokemon API Uncaught TypeError: Cannot read property '0' of undefined - javascript

I'm trying to access the abilities of my pokemon, but I keep getting the same error. I'm using React hooks to build my project, and the data that I fetched from Pokemon API was set to setWildPokemon. If I put wildPokemon.name, I'll get the name of the pokemon, which is fine. This also works when I output wildPokemon.abilities. However, when I start getting deeper into my nested objects, that's when things go
function App() {
const [pokedex, setPokedex] = useState([]);
const [wildPokemon, setWildPokemon] = useState({});
const [storeCard, setStoreCard] = useState({});
const { id, sprites } = wildPokemon;
// console.log(id, sprites.back_shiny);
console.log(wildPokemon);
console.log(wildPokemon.name);
// console.log(wildPokemon.types[0].name);
useEffect(() => {
encounterWildPokemon();
}, []);
const pokeId = () => {
const min = Math.ceil(1);
const max = Math.floor(151);
return Math.floor(Math.random() * (max - min + 1)) + min;
};
const encounterWildPokemon = () => {
axios
.get(`https://pokeapi.co/api/v2/pokemon/${pokeId()}`)
.then(response => {
setWildPokemon(response.data);
});
};
const catchPokemon = pokemon => {
setPokedex(state => {
const monExists = state.filter(p => pokemon.id === p.id).length > 0; // mostly false. Only true if you catch the same pokemon
if (!monExists) {
state = [...state, pokemon];
state.sort(function(a, b) {
return a.id - b.id;
});
}
return state;
});
encounterWildPokemon(); // MISTAKE: we have to call this function whenever we're done
};
const releasePokemon = id => {
setPokedex(state => state.filter(p => p.id != id));
};
// PokeModal
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = pokemon => {
setShow(true);
setStoreCard(pokemon);
};
// JSX
return (
<div className="app-wrapper container">
<header>
<h1 className="title">React Hooks</h1>
{/* <img src="{sprites[0].back_default}" /> */}
<h3 className="subtitle">With Pokémon</h3>
</header>
<section className="wild-pokemon">
<h2>Wild Encounter</h2>
<img
src={
"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/" +
wildPokemon.id +
".png"
}
className="sprite"
alt=""
/>
<h3>{wildPokemon.name}</h3>
<button className="catch-btn" onClick={() => catchPokemon(wildPokemon)}>
CATCH
</button>
</section>
UPDATED:
Ok, I just solved the problem =). I was trying to access the data as soon as the webpage renders. Since fetching is asynchronous, I was basically trying to get the data that hasn't existed yet, which is an empty object.

The state is an empty object on the initial load, and that's what is used for the first render. When you try to access sprites[0], sprites is undefined, since the data hasn't been loaded yet. one way to solve this issue is to delay the render until the data is fetched:
return (
sprites && sprites.length && (
<div className="app-wrapper container">
<header>
<h1 className="title">React Hooks</h1>
<img src={sprites[0].back_default} />
<h3 className="subtitle">With Pokémon</h3>
</header>
...
)
Alternatively you can use a loading state and set it to true until the data is fetched. Helpful when you want to show a loader meanwhile.
const [loading, setLoading] = useState(true);
const encounterWildPokemon = () => {
setLoading(true);
axios
.get(`https://pokeapi.co/api/v2/pokemon/${pokeId()}`)
.then(response => {
setWildPokemon(response.data);
setLoading(false);
});
};
// JSX
return (
loading ? <p>Loading...</p> : (
<div className="app-wrapper container">
<header>
<h1 className="title">React Hooks</h1>
<img src={sprites[0].back_default} />
<h3 className="subtitle">With Pokémon</h3>
</header>
...
)

Related

Unable to set state in a function

So I was trying to build a server side pagination that works like a static one and I'm almost there, But I've encountered some issues which I cannot seem to solve.
This is what my code looks like
const LiveIndex = (props) => {
const [currentPage, setCurrentPage] = useState(0);
const [isLoading, setLoading] = useState(false);
const startLoading = () => setLoading(true);
const stopLoading = () => setLoading(false);
useEffect(() => {
//After the component is mounted set router event handlers
Router.events.on("routeChangeStart", startLoading);
Router.events.on("routeChangeComplete", stopLoading);
return () => {
Router.events.off("routeChangeStart", startLoading);
Router.events.off("routeChangeComplete", stopLoading);
};
}, []);
const paginationHandler = (page) => {
const currentPath = props.router.pathname;
const currentQuery = props.router.query;
currentQuery.page = currentPage + 1;
props.router.push({
pathname: currentPath,
query: currentQuery,
});
setCurrentPage(currentQuery.page);
};
const backToLastPage = (page) => {
const currentPath = props.router.pathname;
const currentQuery = props.router.query;
currentQuery.page = currentPage - 1;
setCurrentPage(currentQuery.page); // THE code that breaks my code.
props.router.push({
pathname: currentPathh,
query: currentQueryy,
});
};
let content;
if (isLoading) {
content = (
<div>
<h2 class="loading-text">loading.</h2>
</div>
);
} else {
//Generating posts list
content = (
<div className="container">
<h2> Live Games - </h2>
<div className="columns is-multiline">
<p>{props.games.name}</p>
</div>
</div>
);
}
return (
<>
<div className={"container-md"}>
<div>{content}</div>
{props.games.length ? (
<a onClick={() => paginationHandler(currentPage)}> moore </a>
) : (
backToLastPage(currentPage)
)}
</div>
</>
);
};
export async function getServerSideProps({ query }) {
const page = query.page || 1; //if page empty we request the first page
const response = await fetch(
`exampleapi.com?sort=&page=${page}&per_page=10&token`
);
const data = await response.json();
return {
props: {
games: data,
},
};
}
export default withRouter(LiveIndex);
The issue is my backToLastPage does the job well but I'm unable to use setCurrentPage() in that function, Every time I use that I get the following error
Uncaught Invariant Violation: Too many re-renders. React limits the number of renders to prevent an infinite loop
How can I possibly update the value of my currentPage state in the backToLast function
Thank you
You're calling backToLastPage directly in JSX which will be re-rendered/re-called every time. And setCurrentPage (with useState) triggers re-rendering for state changes in backToLastPage.
You can imagine that every time the state changes, your component gets rendered and it will set states again that make infinite renderings for the component.
You can use useEffect to handle props.games changes. That will help you to trigger backToLastPage only once whenever props.games get changed.
React.useEffect(() => {
if(!props.games || !props.games.length) {
backToLastPage(currentPage)
}
},[props.games])
Full modification can be
const LiveIndex = (props) => {
const [currentPage, setCurrentPage] = useState(0);
const [isLoading, setLoading] = useState(false);
const startLoading = () => setLoading(true);
const stopLoading = () => setLoading(false);
useEffect(() => {
//After the component is mounted set router event handlers
Router.events.on("routeChangeStart", startLoading);
Router.events.on("routeChangeComplete", stopLoading);
return () => {
Router.events.off("routeChangeStart", startLoading);
Router.events.off("routeChangeComplete", stopLoading);
};
}, []);
//The main change is here
//It will be triggered whenever `props.games` gets updated
React.useEffect(() => {
if(!props.games || !props.games.length) {
backToLastPage(currentPage)
}
},[props.games])
const paginationHandler = (page) => {
const currentPath = props.router.pathname;
const currentQuery = props.router.query;
currentQuery.page = currentPage + 1;
props.router.push({
pathname: currentPath,
query: currentQuery,
});
setCurrentPage(currentQuery.page);
};
const backToLastPage = (page) => {
const currentPath = props.router.pathname;
const currentQuery = props.router.query;
currentQuery.page = currentPage - 1;
setCurrentPage(currentQuery.page); // THE code that breaks my code.
props.router.push({
pathname: currentPathh,
query: currentQueryy,
});
};
let content;
if (isLoading) {
content = (
<div>
<h2 class="loading-text">loading.</h2>
</div>
);
} else {
//Generating posts list
content = (
<div className="container">
<h2> Live Games - </h2>
<div className="columns is-multiline">
<p>{props.games.name}</p>
</div>
</div>
);
}
return (
<>
<div className={"container-md"}>
<div>{content}</div>
{props.games.length && (
<a onClick={() => paginationHandler(currentPage)}> moore </a>
)}
</div>
</>
);
};
export async function getServerSideProps({ query }) {
const page = query.page || 1; //if page empty we request the first page
const response = await fetch(
`exampleapi.com?sort=&page=${page}&per_page=10&token`
);
const data = await response.json();
return {
props: {
games: data,
},
};
}
export default withRouter(LiveIndex);

React.js: filtering API object with 2 search bars

I am fetching an API data set and filtering that data with a search bar to locate by first or last name. I also have an input field that allows you to add "tags" to the data set that I am mapping through. I am trying to add a second search bar to filter the original data by the unique tags as well, but can not figure out how to incorporate that information into the filter.
export default function Home() {
const [students, setStudents] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [search, setSearch] = useState("");
const [showTests, setShowTests] = useState({});
const [tagSearch, setTagSearch] = useState("");
const [tags, setTags] = useState([]);
useEffect(() => {
const getData = async () => {
try {
const response = await axios.get(
<!-- API -->
);
setStudents(response.data);
setError(null);
} catch (err) {
setError(err.message);
setStudents(null);
} finally {
setLoading(false);
}
};
getData();
}, []);
return (
<div className="home-main">
<Search setSearch={setSearch} />
<TagSearch setTagSearch={setTagSearch} />
{loading && <div>Loading, please wait ...</div>}
{error && (
<div>{`An Error has occurred. - ${error}`}</div>
)}
<div className="students">
<Fragment>
{
students
&&
students.students.filter((val) => {
if(search === '' || tagSearch === '') {
return val
} else if(val.firstName.toLowerCase().includes(search.toLowerCase())
|| val.lastName.toLowerCase().includes(search.toLowerCase())
|| tags.text.toLowerCase().includes(tagSearch.toLowerCase()) ){
return val
}
}).map(({val}) => (
<!-- additional info -->
<div className="tags">
<Tags setTags={setTags} />
</div>
</div>
</div>
))
}
</Fragment>
</div>
</div>
);
}
This is where the "tag" state is coming from...
export default function Tags({setTags}) {
const [inputText, setInputText] = useState('');
const [tiles, setTiles] = useState([]);
const inputTextHandler = (e) => {
setInputText(e.target.value);
};
const submitTagHandler = () => {
setTiles([
...tiles, {text: inputText, id: Math.floor(Math.random() * 1000000)}
]);
setTags([
...tiles, {text: inputText}
])
setInputText('');
};
return (
<div className="tags-main">
<div className="tiles-contain">
{
tiles.map((obj) => (
<Tiles key={obj.id} text={obj.text} id={obj.id} tiles={tiles} setTiles={setTiles} />
))
}
</div>
<input value={inputText} onChange={inputTextHandler} onKeyPress={(e) => {
if(e.key === 'Enter') {
if(inputText !== "") {
submitTagHandler();
} else {
alert("Please enter a tag")
}
};
}} placeholder='Add Tag Here' type="text" />
</div>
);
}
It works without the tag state added to the filter. After adding the tag logic neither search bar works. How can I add the array of tags to the filter dependency to sort by first or last name and tags?
I'm pretty sure you were getting an error "cannot read toLowerCase of undefined"
You probably wanted to do something like this
tags.some(tag => tag.text.toLowerCase() === tagSearch.toLowerCase())
or
tags.map(tag => tag.text.toLowerCase()).includes(tagSearch.toLowerCase())

Why will my fetch API call map one nested objects, but not the other?

I'm parsing data from the NASA API using React, and for some reason I can map one nested object within the return but not the other.
Here is my parent component:
import React, { useState } from 'react'
import './NasaAPI.scss'
import NasaImages from './NasaImages'
const NasaAPI = () => {
const [nasaData, setNasaData] = useState([])
const [nasaImage, setNasaImage] = useState("")
const [searchInput, setSearchInput] = useState("")
const [loading, setLoading] = useState(true)
const fetchData = async (e) => {
const data = await fetch(`https://images-api.nasa.gov/search?q=${searchInput}`)
.then(response => response.json())
.then(data => setNasaData(data.collection.items))
.catch(err => console.log(err))
.finally(setLoading(false))
}
const handleSubmit = (e) => {
e.preventDefault()
fetchData()
}
const handleChange = (e) => {
setSearchInput(e.target.value)
}
return (
<div>
<h2>Search NASA Images</h2>
<form onSubmit={handleSubmit}>
<input name="searchValue" type="text" value={searchInput} onChange={handleChange}></input>
<button value="Submit">Submit</button>
</form>
<section>
<NasaImages nasaData={nasaData} loading={loading}/>
</section>
</div>
)
}
export default NasaAPI
Here's where the issue is, in the child component:
import React from 'react'
const NasaImages = ({ nasaData }) => {
console.log(nasaData)
return (
<div>
<h2>This is a where the data go. 👇</h2>
{
nasaData && nasaData.map((data, idx) => {
return (
<div key={idx}>
<p>{data.href}</p>
<div>
{/* {data.links.map((data) => {
return <p>{data.href}</p>
})} */}
{data.data.map((data) => {
return <p>{data.description}</p>
})}
</div>
</div>
)
})
}
</div>
)
}
export default NasaImages
The current configuration works, and will display a data.description (data.data.map) mapping property. However, I want the commented code immediately above it to work which displays a data.href (data.links.map) property.
The JSON looks as follows:
So, the issue is that I can map one set of properties, data.data.map, but cannot access the other in the same object, data.links.map, without getting the error "TypeError: Cannot read property 'map' of undefined". Thank you in advance!
There exists a data element sans a links property, in other words there is some undefined data.links property and you can't map that. Use Optional Chaining operator on data.links when mapping, i.e. data.links?.map. Use this on any potentially undefined nested properties.
const NasaImages = ({ nasaData = [] }) => {
return (
<div>
<h2>This is a where the data go. 👇</h2>
{nasaData.map((data, idx) => (
<div key={idx}>
<p>{data.href}</p>
<div>
{data.links?.map((data, i) => <p key={i}>{data.href}</p>)}
{data.data?.map((data, i) => <p key={i}>{data.description}</p>)}
</div>
</div>
))}
</div>
)
}

TypeError: Cannot read property 'Countries' of undefined. Why an array of objects is not recognized and is not filtered?

I am getting an object from the API ('https://api.covid19api.com/summary'). This object has a
key
Countries with an array of objects and this array of objects I need to filter.
const filteredData = data.Countries.filter(dat => {
return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
})
TypeError: Cannot read property 'Countries' of undefined.
Why an array of objects is not recognized and is not filtered?
In another file, the map method iterates over the same writing data.Countries without error.
const Home = () => {
const [data, setData] = useState();
const [searchfield, setSearchfield] = useState('')
useEffect(() => {
const fetch = async () => {
try{
const res = await axios.get('https://api.covid19api.com/summary');
setData(res.data);
}catch(error){
console.log(error);
}
};
fetch();
}, []);
const onSearchChange = (event) => {
setSearchfield(event.target.value)
}
const filteredData = data.Countries.filter(dat => {
return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
})
return (
<div className="main-container">
<Header searchChange={onSearchChange}/>
<div className="wrapper">
<Card data={data}/>
{/*<div className="graph">
<h1>Global Pandemic Crisis Graph</h1>
<img src={COVID.image} alt=""/>
</div>*/}
<div className="countries">
<Countries data={filteredData}/>
</div>
</div>
{/*<Footer />*/}
</div>
)
}
When you are fetching the data from an api, you need to use optional chaining ? when applying any higher order function to an array just incase the data haven't been loaded. for example
const filteredData = data?.Countries.filter(dat => {
return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
})
Issue
The initial data state is undefined, so data.Countries is undefined on the initial render.
const [data, setData] = useState();
Solution
Provide valid initial state and guard against later bad updates (if they happen).
const [data, setData] = useState({ Countries: [] });
...
const filteredData = data?.Countries?.filter(dat => {
return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
})
You need to filter your data in a callback of the axios response, or it will be "undefined" because it hasn't finished fetching it.
let filteredData = useRef(null);
useEffect(() => {
if (typeof data !== "undefined")
filteredData.current = data.Countries.filter((dat) => {
return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
});
}, [data]);
const fetch = async () => {
const res = await axios
.get("https://api.covid19api.com/summary")
.then((response) => {
// response.data should not be "undefined" here.
setData(response.data);
})
.catch((error) => {
// Error fallback stuff
console.error(error);
});
};
if (!filteredData.current) fetch();
Later in your code you can check whether or not it has been defined,
return (
<div className="main-container">
<div className="wrapper">
{filteredData.current !== null &&
filteredData.current.map((CountryMap, i) =>
<div key={i}>{CountryMap.Country}</div>
)
}
</div>
</div>
);

API request called for the infinity times inside react app

I use react and when I get data from the api i store that data inside a hook and when i console.log that data is called for an infinity times and this lag my website. Here is the code if someone can help i will appreciate =>
//importing components
import Main from './components/Main/Main'
import Second from './components/Second/Second'
//import style
import './App.scss'
// api for testing => https://jsonplaceholder.typicode.com/todos/1
const App = () => {
const [data, setData] = useState() //here we get the data from the API
const [drop, setDrop] = useState(null)
const getValue = (e) => {
setDrop(e.target.value)
}
console.log(data + ' here is the data')
useEffect(() => {
let URL;
if (drop === null) {
URL = 'https://disease.sh/v3/covid-19/all'
} else {
URL = `https://disease.sh/v3/covid-19/countries/${drop}?strict=true`
}
//getting data from the api
fetch(URL).then(res => res.json()).then(data => setData(data))
})
return (
< div className="wrapper" >
<div className="first">
{data !== undefined && <Main info={data} getValue={getValue} />}
<button onClick={() => { console.log(drop) }}>testing</button>
<button onClick={() => { console.log(data) }}>testing API</button>
<button onClick={() => { console.log(data.deaths) }}>testing deaths</button>
</div>
<div className="bla">
<Second />
</div>
</div >
)
}
export default App```
You need to add an empty dependency array to your useEffect. This means it will run only when the component mounts. If you don't pass one in, as you've done, it will run every time the component re-renders. Since the effect sets the state and causes a re-render, this will lead to an infinite loop. Change to this:
useEffect(() => {
let URL;
if (drop === null) {
URL = 'https://disease.sh/v3/covid-19/all'
} else {
URL = `https://disease.sh/v3/covid-19/countries/${drop}?strict=true`
}
//getting data from the api
fetch(URL).then(res => res.json()).then(data => setData(data))
}, [drop]) //empty array added here

Categories

Resources