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.
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