Reputation: 609
Why is this triggering fetchData
multiple times? The console.log
seems to loop almost infinitely? How do I get this to run just once onload and trigger only once when fetchData()
is called later? What am I doing wrong here or missing?
const [data, setData] = useState(null);
let fetchData = React.useCallback(async () => {
const result = await fetch(`api/data/get`);
const body = await result.json();
setData(body);
console.log(data)
},[data])
useEffect(() => {
fetchData();
},[fetchData]);
Update (Additional Question): How to wait for data
to populate before return()
the below this now gives the error because it is null at first?: data.map is not a function
return (
<select>
{data.map((value, index) => {
return <option key={index}>{value}</option>
})}
</select>
)
Upvotes: 0
Views: 7274
Reputation: 6837
In the useCallback
hook you pass data
as a dependency and also simultaneously change the value of data inside the callback by calling setData
, which means every time the value of data changes fetchData
will be reinitialized.
In the useEffect
hook fetchData
is a dependency which means every time fetchData
changes useEffect
will be triggered. That is why you get an infinite loop.
Because you want to fetch data once when the component is mounted, I think useCallback
is unnecessary here. There is no need to memoize the function fetchData
unnecessarily.
Solution
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const result = await fetch(`api/data/get`);
const body = await result.json();
setData(body);
} catch(err) {
// error handling code
}
}
// call the async fetchData function
fetchData()
}, [])
If you want to log the value of data
when it changes, you can use another useEffect
to do that instead. For example,
useEffect(() => {
console.log(data)
}, [data])
P.S. - Also don't let the promise get unhandled, please use a try...catch
block to handle the error. I have updated the solution to include the try..catch block.
Edit - solution for the additional question There are two possible solutions,
Because you expect the value of data
to be an array after the API call you can initialize the value of data as an empty array like,
const [data, setData] = useState([]);
But if for some reason you have to initialize the value of data
as null
. Here is how you can render the information returned from the API call.
// using short-circuit evaluation
return (
<select>
{data && data.length && data.map((value) => {
return <option key={`select-option-${value}`}>{value}</option>
})}
</select>
)
// using a ternary
return (
<div>
{ data && data.length
? (<select>
{
data.map(value => <option key={`select-option-${value}`}>{value}</option>)
}
</select>
)
: <div>Data is still loading...</div>
}
</div>
)
Do not use indexes
as key
, use something unique for the key.
Upvotes: 9