简体   繁体   中英

React not re-rendering after state update

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

Issue

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.

Solution

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM