Reputation: 212
I have a problem when using the useEffect hook, it is generating an infinite loop.
I have a list that is loaded as soon as the page is assembled and should also be updated when a new record is found in "developers" state.
See the code:
const [developers, setDevelopers] = useState<DevelopersData[]>([]);
const getDevelopers = async () => {
await api.get('/developers').then(response => {
setDevelopers(response.data);
});
};
// This way, the loop does not happen
useEffect(() => {
getDevelopers();
}, []);
// This way, infinte loop
useEffect(() => {
getDevelopers();
}, [developers]);
console.log(developers)
If I remove the developer dependency on the second parameter of useEffect, the loop does not happen, however, the list is not updated when a new record is found. If I insert "developers" in the second parameter of useEffect, the list is updated automatically, however, it goes into an infinite loop.
What am I doing wrong?
complete code (with component): https://gist.github.com/fredarend/c571d2b2fd88c734997a757bac6ab766
Upvotes: 0
Views: 177
Reputation: 169338
The dependencies for useEffect
use reference equality, not deep equality. (If you need deep equality comparison for some reason, take a look at use-deep-compare-effect
.)
The API call always returns a new array object, so its reference/identity is not the same as it was earlier, triggering useEffect
to fire the effect again, etc.
Given that nothing else ever calls setDevelopers
, i.e. there's no way for developers
to change unless it was from the API call triggered by the effect, there's really no actual need to have developers
as a dependency to useEffect
; you can just have an empty array as deps: useEffect(() => ..., [])
. The effect will only be called exactly once.
EDIT: Following the comment clarification,
I register a developer in the form on the left [...] I would like the list to be updated as soon as a new dev is registered.
This is one way to do things:
The idea here is that developers
is only ever automatically loaded on component mount. When the user adds a new developer via the AddDeveloperForm
, we opportunistically update the local developers
state while we're posting the new developer to the backend. Whether or not posting fails, we reload the list from the backend to ensure we have the freshest real state.
const DevList: React.FC = () => {
const [developers, setDevelopers] = useState<DevelopersData[]>([]);
const getDevelopers = useCallback(async () => {
await api.get("/developers").then((response) => {
setDevelopers(response.data);
});
}, [setDevelopers]);
useEffect(() => {
getDevelopers();
}, [getDevelopers]);
const onAddDeveloper = useCallback(
async (newDeveloper) => {
const newDevelopers = developers.concat([newDeveloper]);
setDevelopers(newDevelopers);
try {
await postNewDeveloperToAPI(newDeveloper); // TODO: Implement me
} catch (e) {
alert("Oops, failed posting developer information...");
}
getDevelopers();
},
[developers],
);
return (
<>
<AddDeveloperForm onAddDeveloper={onAddDeveloper} />
<DeveloperList developers={developers} />
</>
);
};
Upvotes: 2
Reputation: 48
Use an empty array []
in the second parameter of the useEffect.
This causes the code inside to run only on mount of the parent component.
useEffect(() => {
getDevelopers();
}, []);
Upvotes: 0
Reputation: 2341
The problem is that your getDevelopers
function, calls your setDevelopers
function, which updates your developers
variable. When your developers
variable is updated, it triggers the useEffect
function
useEffect(() => {
getDevelopers();
}, [developers]);
because developers
is one of the dependencies passed to it and the process starts over.
Every time a variable within the array, which is passed as the second argument to useEffect, gets updated, the useEffect
function gets triggered
Upvotes: 0