I made a react.js app based on the PokeAPI which contains an homepage like the following, where a list of pokemon is obtained and a grid with pokemons' names and pictures is created.
import axios from "axios"
import { useEffect, useState } from "react";
import Navbar from "../components/Navbar";
import Pagination from "../components/Pagination";
import PokemonCard from "../components/PokemonCard";
import Title from "../components/Title";
import pokedex from "../img/pokedex.png"
export default function Homepage() {
const limit = 64
const nPages = 6
const [ pokemon, setPokemon ] = useState(null)
const [ totalCount, setTotalCount ] = useState(0)
const [ currentPage, setCurrentPage ] = useState(1)
const [ maxPageLimit, setMaxPageLimit ] = useState(6);
const [ minPageLimit, setMinPageLimit ] = useState(0);
const [ filter, setFilter ] = useState("")
const getPokemon = async () => {
try{
const response = await axios.get(`https://pokeapi.co/api/v2/pokemon?offset=${limit*(currentPage-1)}&limit=${limit}`);
setTotalCount(response.data.count)
setPokemon(response.data.results);
console.log(pokemon)
}catch(error){
console.log(`Error fetching data: ${error}`)
}
}
useEffect( () => {
getPokemon();
}, [currentPage] )
function filterPokemon(){
const pokemonCopy = [...pokemon]
if (filter === ""){
return pokemonCopy
}else{
return pokemonCopy.filter(p => (p.name.toLowerCase()).includes(filter))
}
}
const onChangeFilter = (event) =>{
setFilter(event.target.value);
}
const onResetPage = () => {
setCurrentPage(1)
}
const onPageChange = (pageNumber) =>{
setCurrentPage(pageNumber)
}
const onPrevClick = () =>{
if((currentPage-1) <= minPageLimit){
setMaxPageLimit(maxPageLimit - nPages);
setMinPageLimit(minPageLimit - nPages);
}
setCurrentPage(prev=> prev-1);
}
const onNextClick = () => {
if(currentPage 1 > maxPageLimit){
setMaxPageLimit(maxPageLimit nPages);
setMinPageLimit(minPageLimit nPages);
}
setCurrentPage(prev=>prev 1);
console.log("next page limits: ", maxPageLimit, minPageLimit);
}
return (
<div className="Homepage">
<Title></Title>
<Navbar onResetPage={onResetPage}></Navbar>
<form className="Form">
<label for="search">Search Pokemon</label>
<input id="search" value={filter} onChange={onChangeFilter} />
</form>
<div id="grid-container">
{
!pokemon ? (
<div className="Loading">
<p>Loading Pokedex...</p>
<img src={pokedex} alt=""></img>
</div>
) : filterPokemon().map((p,index) => {
console.log(p)
return <PokemonCard
key={index}
name={p.name}
></PokemonCard>
} )
}
</div>
{ pokemon ? (
<Pagination
count={totalCount}
totalPages={Math.ceil(totalCount / limit)}
currentPage={currentPage}
min={minPageLimit}
max={maxPageLimit}
onPrevClick={onPrevClick}
onNextClick={onNextClick}
onPageChange={onPageChange}
></Pagination>) : ""}
</div>
)
}
The problem is that the <PokemonCard>
component, which is responsible for individually reporting the information about a single pokemon, does not update when I move to a different page and thus make a new API call, updating the state and triggering, in theory, a re-render. Here is the component:
import { useState, useEffect, useRef } from "react";
import axios from "axios";
import Modal from "./Modal";
export default function PokemonCard({name}) {
const [ singlePokemon, setSinglePokemon ] = useState(null)
const [ isShown, setIsShown ] = useState(false)
const refEl = useRef(null)
useEffect( () => {
getSinglePokemon()
document.addEventListener("click", handleClickOutside, true)
}, [])
const handleClickOutside = (e) => {
if (!refEl.current.contains(e.target) || refEl.current.contains(e.target) === null){ //null-check in order to fix a warning
setIsShown(false)
}
}
const getSinglePokemon = async () => {
try{
const response = await axios.get(`https://pokeapi.co/api/v2/pokemon/${name}`);
setSinglePokemon(response.data);
}catch(error){
console.log(error)
}
}
const renderModal = () =>{
setIsShown(true)
}
function capitalize(string){
return string.charAt(0).toUpperCase() string.slice(1)
}
return (
<div id="grid-item">
{singlePokemon ? (
<img
onClick={ () => renderModal()}
src={singlePokemon.sprites.versions["generation-v"]["black-white"].animated.front_default}
alt={singlePokemon.name}
ref={refEl}
></img>
) : "Loading"}
{isShown && singlePokemon ? (
<Modal setIsShown={() => {setIsShown()}} par={["Pokedex: " singlePokemon.id, "Name: " capitalize(singlePokemon.name), "Weight: " singlePokemon.weight,
singlePokemon.types.map( (t,i) => {
return(`Type ${i 1}: ${t.type.name} `)
})
]}></Modal>
) : ""}
</div>
)
}
I suspect that this is related to the fact that I need to make a single API call for every PokemonCard element, which goes out of sync with the homepage. The problem is that the PokeAPI does not give me access to the animated gif parameter when I receive the whole list of pokemon. How can I solve this?
CodePudding user response:
You're updating the component prop (name), but since it is not in the useEffect
dependency array, it doesn't trigger your useEffect
again, so it never fetches the new pokemon.
Adding name
to the dependency array should fix your problem:
useEffect( () => {
getSinglePokemon()
document.addEventListener("click", handleClickOutside, true)
}, [name])