Reputation: 41
I am struggling to understand why my code is not working. I am using the useEffect() hook to make a call to an API, and then I am using setState() to update my component state. In my JSX, I am mapping my info array to render the data.
Here's my code:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import './App.css';
function App() {
const [info, setInfo] = useState();
console.log(info);
useEffect(() => {
const getUsers = async () => {
const res = await axios('https://api.mocki.io/v1/b043df5a');
console.log(res.data);
setInfo(res.data);
};
getUsers();
}, []);
return (
<div>
<input type='text' placeholder='Search users' />
<input type='text' placeholder='Search users' />
{info.map((el, index) => {
console.log(el);
return <h1 key={index}>{el.city}</h1>;
})}
</div>
);
}
export default App;
However, I get this error: 'TypeError: Cannot read property 'map' of undefined'. My best guess is that my JSX is rendered before my state is populated with API info.
The code does work when I write the following JSX, which allows me to check if 'info' is true:
{info && info.map((el, index) => {
console.log(el);
return <h1 key={index}>{el.city}</h1>;
})}
Is this a normal behavior? Why is useEffect not populating my state before my page is rendered?
I would appreciate your help, as I am struggling to find the solution to this specific issue.
Thanks!
Upvotes: 4
Views: 6928
Reputation: 364
A use Effect with a [] as second can be interpreted as a 'componentDidMount'
To give a very simple answer, your code is 'executed' the first time without the useEffect. So indeed, 'info' will not exist. At this point your 'info' variable is not yet defined. Then, when your component 'is mounted', it will execute the code in your useEffect. Only then info will be filled in.
I would recommend to go through this documentation to fully understand this: https://reactjs.org/docs/state-and-lifecycle.html
Upvotes: 0
Reputation: 26
So basicly useEffect
function is equivalent of componentDidMount
. What I'm trying to say your component renders and then it executes function passed in useEffect
. To avoid this eighter use check that you introduced as a fix yourself or pass default value to useState
method. I would suggest your option but with condition if info
exists show it and if it's not then show some kind of loading indicator.
Upvotes: 0
Reputation: 5508
You are trying to iterate over undefined
it's normal cause you need to check first before data will come to your state. You can achieve this in two ways:
info && info.map
But your initial state must be falsy value like:
useState(null)
Or better way to set initial state to empty array and map will run when data will come and you will get no error.
Upvotes: 0
Reputation: 6582
that's because useEffect
hook will run after the dom render
phase finished and one more thing that can cause the delay of getting data is the fact that you're calling an asynchronous function which usually get some time to finished.
so what are the possible options here:
just use empty array []
as default value
check the length of the state like info.length && whatever...
sometimes you can use the useLayoutEffect
which is kinda a synchronous operation. but in your case which is an api calls solution 1
and 2
is the answer
Upvotes: 1
Reputation: 706
Just do this:
const [info, setInfo] = useState([]);
The issue is that you have no intial value and therefore it automatically defaults to undefined. Now you are trying to call .map
on a value that is undefined therefore it throws an error. With an empty array as the initial value however, .map
will loop over an empty array on the first render (before the useEffect) and it won't throw any error.
Upvotes: 1