Search code examples
javascriptreduxasync-awaitthunk

Wait for API fetch to complete before defining const in Redux


I'm attempting to render a page of Pokemon with their attributes (when selected) in react-redux.

I have a PokemonDetail component

export function pokemonDetail (props) {
    const { id } = useParams()
    const moves = Object.values(props.state.entities.moves).map((val) => val.name)
    const thisPokemon = props.state.entities.pokemon[id]
    

    useEffect(() => {
        props.requestSinglePokemon(id)
    }, [id])


    return(
            <section className="pokemon-detail">
                <ul>
                    <figure>
                        <img src={thisPokemon.imageUrl} alt={thisPokemon.name} />
                    </figure>
                    <li><h2>{thisPokemon.name}</h2></li>
                    <li>Type: {thisPokemon.pokeType}</li>
                    <li>Attack: {thisPokemon.attack}</li>
                    <li>Defense: {thisPokemon.defense}</li>
                    <li>Moves: {moves.join(', ')}</li>
                </ul>
            </section>

        )
}

This component makes a request to grab a Pokemon via a thunk action

export const requestSinglePokemon = (id) => (dispatch) => {
    APIUtil.fetchPokemon(id).then(
        (payload) => (dispatch(receiveOnePokemon(payload)))
    )
}

The problem is, the moves object will be blank {} until the request completes, where it will then be populated with the Pokemon, moves, and items. When it tries to initially render, then, it will throw an error that it's trying methods on a blank object. An easy fix to this is to simply write if (thisPokemon)... { but I'm wondering if there's a way to make my constants wait for the request to complete before being defined.


Solution

  • Without seeing how the props interact with each other or the exact implementation of the requestSinglePokemon, I can only give you a workaround where you define a local variable that reacts to thisPokemon and use that to conditional render. Basically what you are doing with the if (thisPokemon) code, but easier to read

    Basically, you could define a loading state and render a loader accordingly to the truthy-iness of thisPokemon.

    E.g.

    // simplified example
    
    
    // ... all of your imports
    
    import {useMemo} from 'react'
    
    function PokemonDetail () {
      // ... all of your code
    
      // when thisPokemon is falsy, loaded is false
      // when thisPokemon is truthy, loaded is true
      const loaded = useMemo(() => {
        return Boolean(thisPokemon);
      }, [thisPokemon])
      
    
      return <>
        {!loaded&& {'Data is loading...'}}
        {loaded && <>{/* your JSX */}</>}
      </>
    }
    
    

    A better solution is to simply modify the parent props and conditional the component as a whole, so the loading logic is done at the container instead of the component. This way, the PokemonDetail component does not have to care about undefined thisPokemon conditions.

    i.e. Do the conditional rendering at the parent component