Reputation: 647
I'm creating a React/Redux app that fetches data from an api (pokeapi.co). I fetch data using axios
. When I display data on react components, it results in an error that data is undefined
. After some digging, I find that my state at first returns initial state which is empty object then it returns the api data. but it dont display on react. I'm new to React so I'm guessing it has to do with the axios asynchronous functionality. How do you set state with api's initial data or wait on rendering the data till state has api's data?
Here is the Reducer
function pokemonReducer(state={}, action) {
switch (action.type) {
case pokemonsActions.GET_POKEMON_SUCCESS:
{
return {...state, data: action.payload.data}
}
default:
{
return state;
}
}
}
export default pokemonReducer
Here is the action
export const GET_POKEMON_SUCCESS = 'GET_POKEMON_SUCCESS'
export const GET_POKEMON_ERROR = 'GET_POKEMON_ERROR'
function getPokemonSuccess(response) {
return {
type: GET_POKEMON_SUCCESS,
payload: response
}
}
function getPokemonError(err) {
return {
type: GET_POKEMON_ERROR,
payload: err
}
}
export function getPokemon() {
return (disp,getState) =>
{
return pokeAPI.getPokeAPI()
.then((response) => { disp(getPokemonSuccess(response))})
.catch((err)=> disp(getPokemonError(err)))
}
}
Store
const loggerMiddleware = createLogger()
const middleWare= applyMiddleware(thunkMiddleware,loggerMiddleware);
const store = createStore(rootReducer,preloadedState,
compose(middleWare, typeof window === 'object' && typeof window.devToolsExtension !== 'undefined'
? window.devToolsExtension() : (f) => f
))
const preloadedState=store.dispatch(pokemonActions.getPokemon())
export default store
in React component
function mapStateToProps(state) {
return {
pokemons:state.pokemons
}
}
class PokemonAbility extends React.Component {
render(){
return (
<div>
<div className="header">
<h1>Fetch Poke Api with axios</h1>
</div>
<main>
<h3> Display pokemons abilities </h3>
<p>{this.props.pokemons.data.count}</p>
</main>
</div>
)
}
}
export default connect(
mapStateToProps
)(PokemonAbility)
Api data example
{
"count": 292,
"previous": null,
"results": [
{
"url": "https://pokeapi.co/api/v2/ability/1/",
"name": "stench"
},
{
"url": "https://pokeapi.co/api/v2/ability/2/",
"name": "drizzle"
},
{
"url": "https://pokeapi.co/api/v2/ability/3/",
"name": "speed-boost"
}
],
"next": "https://pokeapi.co/api/v2/ability/?limit=20&offset=20"
}
Upvotes: 4
Views: 5575
Reputation: 32066
You're rendering your component before the data has loaded. There are many strategies for dealing with this. In no particular order, here are some examples:
You can short circuit the render by returning a loading message if the data isn't there:
function mapStateToProps(state) {
return {
pokemons:state.pokemons
}
}
class PokemonAbility extends React.Component {
render(){
if (!this.props.pokemons.data) {
return (
<div>Loading...</div>
);
}
return (
<div>
<div className="header">
<h1>Fetch Poke Api with axios</h1>
</div>
<main>
<h3> Display pokemons abilities </h3>
<p>{this.props.pokemons.data.count}</p>
</main>
</div>
);
}
}
export default connect(mapStateToProps)(PokemonAbility);
You can move the mapStateToProps
into a higher component, or abstract out the view component, and only render the view when the data is ready:
function mapStateToProps(state) {
return {
pokemons:state.pokemons
}
}
class SomeHigherComponent extends React.Component {
render(){
return (
this.props.pokemons.data ?
<PokemonAbility pokemons={this.props.pokemons} /> :
<div>Loading...</div>
);
}
}
You could wrap your components in a "higher order component" (a function that takes a component class and returns a component class) to check if that prop exists before rendering:
function EnsurePokemon(ChildComponent) {
return class PokemonEnsureWrapper extends React.Component {
render() {
return (
this.props.pokemons.data ?
<ChildComponent {...this.props} /> :
<div>Loading...</div>
);
}
}
}
Usage:
export default connect(
mapStateToProps
)(EnsurePokemon(PokemonAbility))
And you can wrap any child component in this EnsurePokemon
HOC to make sure it doesn't render until the data has loaded.
Upvotes: 6