I'm stuck in an infinite loop in useEffect despite having tried the clean up function
I tried to pass [] and [usersDB] as the 2nd parameter of useEffect and it didn't work because [] makes it run only one time and it's not the behavior I'm looking for (I want it to run after each update)
const ListUsers = () => {
const [usersDB, setUsersDB] = useState([]);
useEffect(() => {
const getUsers = async () => {
const response = await axios
.get("http://localhost:4000/contacts")
.catch((error) => console.log(error.resp));
setUsersDB(response.data);
};
getUsers();
});
console.log(usersDB);
return (
<div>
<div>
{usersDB.map((user, index) => (
<CardUsers key={user._id} user={user} />
))}
</div>
</div>
);
};
export default ListUsers;
Every time the component renders, it runs your useEffect. Your useEffect calls setUsersDB, which updates state, which causes a rerender, which runs useEffect...
You can add an array of values at the end of useEffect which tells the useEffect to only run if any of the included values have changed. But if you add an empty array, it tells the useEffect to run only once, and never again, as there are no values that it depends on that have changed. You mention that you tried this, and that you want useEffect to run after each update. Each update of what? If there is some other value that is being updated elsewhere, and you want useEffect to be dependent on that value, then stick it in the dependency array.
useEffect(() => {
const getUsers = async () => {
const response = await axios
.get("http://localhost:4000/contacts")
.catch((error) => console.log(error.resp));
setUsersDB(response.data);
};
getUsers();
}, [some updating value])
Related
In useEffect in my react component I get data and I update a state, but I don't know why the useEffect is always executed:
const Comp1 = () => {
const [studies, setStudies]= useState([]);
React.useEffect( async()=>{
await axios.get('/api/expert/',
)
.then((response) => {
setStudies(response.data.studies);
}, (error) => {
console.log(error );
})
console.log("called+ "+ studies);
},[studies]);
return(
<Comp2 studies={studies}/>
)
}
Here is my second Component used in the first component...
const Comp2 = (props) => {
const [studies, setStudies]= useState([]);
React.useEffect( ()=>{
setStudies(props.studies)
},[props.studies, studies]);
return(
studies.map((study)=>{console.log(study)})
}
EDIT
const Comp2 = (props) => {
// for some brief time props.studies will be an empty array, []
// you need to decide what to do while props.studies is empty.
// you can show some loading message, show some loading status,
// show an empty list, do whatever you want to indicate
// progress, dont anxious out your users
return (
props.studies.map((study)=>{console.log(study)}
)
}
You useEffect hook depends on the updates that the state studies receive. Inside this useEffect hook you update studies. Can you see that the useEffect triggers itself?
A updates B. A runs whenever B is updated. (goes on forever)
How I'd do it?
const Comp1 = () => {
const [studies, setStudies]= useState([]);
React.useEffect(() => {
const asyncCall = async () => {
await axios.get('/api/expert/',
)
.then((response) => {
setStudies(response.data.studies);
}, (error) => {
console.log(error );
})
console.log("called+ "+ studies);
}
asyncCall();
}, []);
return(
<Comp2 studies={studies}/>
)
}
useEffect() has dependency array which causes it to execute if any value within it updates. Here, setStudies updates studies which is provided as dependency array and causes it to run again and so on. To prevent this, remove studies from the dependency array.
Refer: How the useEffect Hook Works (with Examples)
In the code below I was expecting that inside the useEffect hook console.log procedure will log the list of the tickets fetched by getAllTickets function because list of the tickets is returned without error.However console.log results in logging an empty array that I set up in the state initially. It seems that console.log is somehow preceding the fetch process and following setTickets hook. Could someone help to clarify this confusion?
import ReactDOM from 'react-dom';
import * as React from 'react';
import { getAllTickets } from './api';
const App = () => {
const [tickets, setTickets] = React.useState([]);
React.useEffect(() => {
getAllTickets()
.then((res) => setTickets([...res]))
.then(() => console.log(tickets))
.catch((e) => console.log(e));
}, []);
const renderTickets = tickets.map((t) => (
<div key={t.id}>
<p>{t.description}</p>
</div>
));
return (
<div>
{renderTickets}
<button onClick={() => setOpen(true)}>Add ticket</button>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
getAllTickets fetches list of tickets from db as below
export const getAllTickets = async () => {
const res = await fetch('http://localhost:3001/tickets');
const data = await res.json();
console.log(data);
return data;
};
It's not that there's a sequencing issue. It's that the function logging the tickets value is logging an old value.
Your useEffect callback is created each time your component function is called, but because your dependency array is empty, only the first of those functions is ever called by useEffect. That first function closes over the tickets constant for the call to your component function that created the callback (the first call), in which tickets is an empty array. So although setTickets updates your state item, which will cause your component function to be called again, your useEffect callback (or more accurately, the callbacks it creates) continue to use the old value.
If you want to see the updated array, you can use res:
React.useEffect(() => {
getAllTickets()
.then((res) => {
const ticketsReceived = [...res]; // Why the shallow copy?
setTickets(ticketsReceived);
console.log(ticketsReceived);
})
.catch((e) => console.log(e));
}, []);
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>
)
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.
I m traying to create a component using Hooks.
Inside useEffect I call a function, but it dosen 't work. I think is because when the app starts the props is empty and in the second, when is full dosen 't call the function again..
WHY I m doing wrong?
Thanks (sorry for my bad english)
this is the code:
import React, { useState, useEffect } from "react";
const fetchContent = async content => {
console.log(content) // is empty in the first console, and full in the second.
const data = [];
for await (const item of content) {
data.push({ componentName: item.nombre});
}
return data;
};
function ContentGroups(props) {
const [contentResult, setResult] = useState([]);
useEffect(() => {
fetchContent(props.info).then(data => setResult(data));
console.log(contentResult) // is empty in the first console, and full in the second.
}, []);
return (
<React.Fragment>
{contentResult.map((el, index) => {
switch (el.componentName) {
case "top":
return <h1>soy un top words</h1>/*
}
})}
</React.Fragment>
);
}
Try adding a props dependency in your useEffect deps array:
useEffect(() => {
fetchContent(props.info).then(data => setResult(data));
}, [props.info]);
Why your example not working?
First, you logging your data while setState is async, console.log(contentResult) will log the current state before the update.
Secondly, you run your useEffect with empty dep array which means you run it once on the component mount.
console.log prints before getting the result
useEffect(() => {
fetchContent(props.info).then(data => {
setResult(data);
console.log(data);
});
}, []);
State
const [user, setUser] = useState({});
checkIfUserIsEnabled()
async function checkIfUserIsEnabled() {
const res = await fetch("http://localhost:8080/users/finduserbytoken?id=" +
getTokenIdFromURL);
res.json()
.then(res => setUser(res))
.catch(err => setErrors(err));
}
useEffect When i call my checkIfUserIsEnabled() in the useEffect below it gets rendered once and displays the false version in the return method.
useEffect(() => {
verifyEmail(getTokenIdFromURL);
checkIfUserIsEnabled();
return () => {
/* cleanup */
};
}, [/* input */])`
useEffect (2th) If i do it like this instead, it keeps spamming the requests towards my API and displays true.
useEffect(() => {
checkIfUserIsEnabled();
});
Return
return (
<div className="emailVerificationWrapper">
{user.enabled
? <h1>Thank you for registrating, {user.firstName}. Account is verified!</h1>
: <h1>Attempting to verify account...</h1>}
</div>
)
To my question(s): Why does the second useEffect spam the request? and is there a way i can make the request being rendered every ~2-3 second instead of the spam? and could i make it stop doing the request once it actually returns true?
The effect hook runs when the component mounts but also when the component updates. Because we are setting the state after every data fetch, the component updates and the effect runs again.
It fetches the data again and again. That's a bug and needs to be avoided. We only want to fetch data when the component mounts. That's why you can provide an empty array(or something which doesn't change) as second argument to the effect hook to avoid activating it on component updates(or only when that parameter changes) but only for the mounting of the component.
let URL = `http://localhost:8080/users/finduserbytoken?id=`;
async function checkIfUserIsEnabled() {
const res = await fetch(`$(URL)` +
getTokenIdFromURL);
res.json()
.then(res => {setUser(res); return Promise.resolve()})
.catch(err => {setErrors(err); return Promise.reject()});
}
useEffect(() => {
const abortController = new AbortController();
const fetchData = async() => await checkIfUserIsEnabled();
fetchData();
return () => {
abortController.abort();
};
}, [URL]);
useEffect(() => {
checkIfUserIsEnabled();
}); <-- no dependency
As your useEffect doesn't have any dependency it will run on every render, so every time you change some state and your component re-renders it will send requests.