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.
EDIT:
Here's what the API call returns: pokeapi.co/api/v2/pokemon/ditto
I also debugged Home and this is what it looks like: imgur.com/a/jVhaSsk
I'm confused now because pokemon.name clearly exists and is not undefined. Why are they not populating in the h3s?
EDIT 2: I changed the home component to this:
<Grid header='Popular pokemons'>
{
state.results.map(pokemon => {
console.log(pokemon.name)
return <h3>pokemon name</h3>
})
}
</Grid>
And it looks like the h3s are still empty. Is it still waiting for the promises in the array? But the console.logs are printing out the pokemon names fine.
EDIT 3:
Thanks to Drew, I found where the problem lies.
This is what my Grid component looks like:
import React from "react";
import { Wrapper, Content } from './Grid.styles';
const Grid = ({ header }) => (
<Wrapper>
<h1>{header}</h1>
</Wrapper>
);
export default Grid;
I'm using styled-components as well, and here's the file:
import styled from 'styled-components';
export const Wrapper = styled.div`
max-width: var(--maxWidth);
margin: 0 auto;
padding: 0 20px;
h1 {
color: var(--medGrey);
@media screen and (max-width: 768px) {
font-size: var(--fontBig);
}
}
`;
export const Content = styled.div`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-gap: 2rem;
`;
As you can see, Content isn't in Wrapper. After I changed it to:
const Grid = ({ header, children }) => (
<Wrapper>
<h1>{header}</h1>
<Content>{children}</Content>
</Wrapper>
);
It worked perfectly. It looks like the array of Pokemons are being passed to Grid as {children}, so when it doesn't exist well it doesn't render it.
