I'm currently learning React. In my homepage component, I'm using a hook to initialize and populate the state. I have 2 states, 1 contains a random pokemon and another is an array of 20 random pokemons. The first one works fine, but not the array.
Here's the homepage component:
// Hook
import { useHomeFetch } from "../hooks/useHomeFetch";
// Image
import NoImage from '../images/missingno.png';
const Home = () => {
const { state, collection, loading, error } = useHomeFetch();
return (
<>
{ state.heroPokemon ?
<HeroImage
image={`${API.fetchSprite(state.heroPokemon)}`}
title={state.heroPokemon.name}
text='Placeholder, should include abilities, etc. to show the pokemon off.'
/>
: null}
{ collection.pokemons[0] ?
<Grid header='Popular pokemons'>
{collection.pokemons.map( pokemon => (
<h3 key={pokemon.id}>{pokemon.name}</h3>
) )}
</Grid>
: null}
</>
);
}
export default Home;
The heroPokemon works fine after I added the conditional rendering. My first thought was to do the same thing with the collection, as it might have rendered before all of the API call promises have resolved.
If I look at the react dev tools, this is what I see for the state:
hooks
HomeFetch
1 State : {heroPokemon: {…}}
2 State : {pokemons: Array(20)}
pokemons :
[{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, …]
So it looks to me that the call indeed completed fine with 20 random pokemon objects in there.
Here's is the hook:
const initialState = {
heroPokemon: null
}
const fetchRandomPokemons = async (ids) => {
let pokemons = [];
ids.forEach(async num => {
pokemons.push(await API.fetchPokemon(num));
});
return pokemons;
}
export const useHomeFetch = () => {
const [state, setState] = useState(initialState);
const [collection, setCollection] = useState({pokemons: []});
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const fetchPokemons = async (limit = 20) => {
try {
setError(false);
setLoading(true);
const heroPokemon = await API.fetchPokemon(randomInt(1, 898));
const randomInts = randomUniqueInt(1, 898, limit);
const pokemons = await fetchRandomPokemons(randomInts);
setState(prev => ({heroPokemon}));
setCollection(prev => ({pokemons}));
} catch (error) {
setError(true);
}
setLoading(false);
};
// Initial render
useEffect(() => {
fetchPokemons(20);
}, [])
return { state, collection, loading, error };
};
When I take out the conditional, Grid
shows up, but it doesn't have the h3s with the pokemon names in them. If I console.log out state
and collection
right before the return in home
. The last few ones show the correct state.heroPokemon
and collection.pokemons
populated correctly. This leads me to believe that the states are updated correctly, but why is React not re-rendering the grid component?
To add, I also tried adding this:
<p>{collection.pokemons[0]}</p>
To the home component, and nothing shows up. I feel like I may be misunderstanding or missing a crucial part of how states and re-renders work, which is leading me to missing what I could be doing wrong.
Try this:
// Hook
import { useHomeFetch } from "../hooks/useHomeFetch";
// Image
import NoImage from '../images/missingno.png';
const Home = () => {
const [newCollection, setNewCollection] =useState([])
const { state, collection, loading, error } = useHomeFetch();
useEffect(()=>{
setNewCollection(collection || []);
},[collection])
return (
<>
{ state.heroPokemon ?
<HeroImage
image={`${API.fetchSprite(state.heroPokemon)}`}
title={state.heroPokemon.name}
text='Placeholder, should include abilities, etc. to show the pokemon off.'
/>
: null}
{ newCollection.pokemons[0] ?
<Grid header='Popular pokemons'>
{newCollection.pokemons.map( pokemon => (
<h3 key={pokemon.id}>{pokemon.name}</h3>
) )}
</Grid>
: null}
</>
);
}
export default Home;
if still not work than share your repo. so I can help you more. Thanks
It seems the issue is in your fetchRandomPokemons
utility function. Even though you've declared fetchRandomPokemons
async
you are not waiting for anything. Array.prototype.forEach
is also completely synchronous as well. You did however declare the .forEach
callback to async
and await
the API response, but because .forEach
is synchronous the fetchRandomPokemons
function has already returned the empty pokemons
array and resolved. The async
keyword only allows a function scope to use the await
keyword.
const fetchRandomPokemons = async (ids) => {
let pokemons = [];
ids.forEach(async num => { // <-- synchronous
pokemons.push(await API.fetchPokemon(num));
});
return pokemons; // <-- returns []
}
...
const pokemons = await fetchRandomPokemons(randomInts); // <-- []
setCollection({ pokemons }); // <-- { pokemons: [] }
What you are logging is the pokemons
array that the .forEach
array mutated when each API.fetchPokemon
Promise resolved.
Map the ids
to an array of Promises and wait them all to resolve and return the resolved array of pokemons.
const fetchRandomPokemons = (ids) => {
return Promise.all(ids.map(API.fetchPokemon));
}
...
const fetchPokemons = async (limit = 20) => {
setError(false);
setLoading(true);
try {
const heroPokemon = await API.fetchPokemon(randomInt(1, 898));
const randomInts = randomUniqueInt(1, 898, limit);
const pokemons = await fetchRandomPokemons(randomInts);
setState({ heroPokemon });
setCollection({ pokemons });
} catch (error) {
setError(true);
}
setLoading(false);
};
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.