About infinite loop in useEffect - reactjs

I am trying to build a redux process with react hooks, the code below is that I want to simulate a ComponentDidMount function with a getUsers(redux action) call in it which is a http request to fetch data.
The first version was like this
const { state, actions } = useContext(StoreContext);
const { getUsers } = actions;
useEffect(() => {
getUsers(); // React Hook useEffect has a missing dependency: 'getUsers'.
}, []);
but I got a linting warning "React Hook useEffect has a missing dependency: 'getUsers'. Either include it or remove the dependency array" in useEffect,
and then I added getUsers to dependency array, but got infinite loop there
useEffect(() => {
getUsers();
}, [getUsers])
Now I find a solution by using useRef
const fetchData = useRef(getUsers);
useEffect(() => {
fetchData.current();
}, []);
Not sure if this is the right way to do this, but it did solve the linting and the infinite loop (temporarily?)
My question is:
In the second version of the code, what exactly caused the infinite loop? does getUsers in dependency array changed after every render?

Your function have dependencies and React deems it unsafe not to list the dependencies. Say your function is depending on a property called users. Listing explicitly the implicit dependencies in the dependencies array won't work:
useEffect(() => {
getUsers();
}, [users]); // won't work
However, React says that the recommended way to fix this is to move the function inside the useEffect() function. This way, the warning won't say that it's missing a getUsers dependency, rather the dependency/ies that getUsers depends on.
function Example({ users }) {
useEffect(() => {
// we moved getUsers inside useEffect
function getUsers() {
console.log(users);
}
getUsers();
}, []); // users dependency missing
}
So you can then specify the users dependency:
useEffect(() => {
function getUsers() {
console.log(users);
}
getUsers();
}, [users]); // OK
However, you're getting that function from the props, it's not defined in your component.
What to do then? The solution to your problem would be to memoize your function.
useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).
You can't memoize it within your component as there will be the same warning:
const memoizedGetUsers = useCallback(
() => {
getUsers();
},
[], // same warning, missing the getUsers dependency
);
The solution is to memoize it right where the getUsers is defined and you will be then be able to list the dependencies:
// wrap getUsers inside useCallback
const getUsers = useCallback(
() => {
//getUsers' implementation using users
console.log(users);
},
[users], // OK
);
And in your component, you'll be able to do:
const { getUsers } = actions; // the memoized version
useEffect(() => {
getUsers();
}, [getUsers]); // it is now safe to set getUsers as a dependency
As to the reason why there was an infinite loop and why useRef worked. I'm guessing your function causes a rerender and at each iteration, getUsers was recreated which ends up in an endless loop. useRef returns an object { current: ... } and the difference between using useRef and creating this object { current: ... } yourself is that useRef returns the same object and doesn't create another one. So you were propbably using the same function.

You should declare getUsers inside useEffect it it's the only place you call getUsers
useEffect(() => {
const { getUsers } = props;
getUsers();
}, []);

By my information on React hooks the job of the second parameter is to be used as a variable of comparision as it would have been in shouldComponentUpdate.
By my understanding of the question getUsers is a function and not a variable that might changeon some condition and hence the infinite loop. Try passing a props that would change after getUsers is called.
import React, { useState,useEffect } from 'react';
function Input({getInputElement}) {
let [todoName, setTodoName] = useState('');
useEffect (() => {
getInputElement(todoName);
},[todoName]);
return (
<div>
<input id={'itemNameInput'} onChange={(e) => setTodoName(e.target.value)} value={todoName} />
</div>
);
}
export default Input;
useEffect : useEffect is a combination of componentDidMount, componentDidUpdate and shouldComponentUpdate. While componentDidMount runs after first render and componentDidUpdate runs after every update, useEffect runs after every render and thus covers both the scenarios. The second optional param to useEffect is the check for shouldComponentUpdate in our case [todoName].This basically checks if todoName has changed after rerender then only do whatever is inside useEffect function.
Hope this helps!

Related

useEffect dependency cause an infinite loop

function Reply({ id, user }) {
const [data, setData] = useState([]);
const [replyText, setReplyText] = useState("");
useEffect(() => {
async function fetchData() {
const response = await _axios.get("/reply/" + id);
setData(response.data);
}
fetchData();
}, [data]); <---- ** problem ** with data(dependency),
infinite request(call) fetchData()
...
}
what's the reason for infinite loop if there's a dependency.
as far as i know, when dependency(data) change, re-render.
but useEffect keep asking for data(axios.get(~~)).
if i leave a comment, i can see normally the latest comments, but the network tab(in develop tools) keeps asking for data(304 Not Modified, under image)
There's an infinite loop because that code says "If data changes, request information from the server and change data." The second half of that changes data, which triggers the first half again.
You're not using data in the callback, so it shouldn't be a dependency. Just remove it:
useEffect(() => {
async function fetchData() {
const response = await _axios.get("/reply/" + id);
setData(response.data);
}
fetchData();
}, []);
// ^^−−−−−−−−−− don't put `data` here
That gives you a blank dependency array, which will run the effect only when the component first mounts. (If you want to run it again after mount, use a different state member for that, or define fetchData outside the effect and use it both in the effect and at the other time you want to fetch data.)
Side note: Nothing in your code is handling rejections from your fetchData function, which will cause "unhandled rejection" errors. You'll want to hook up a rejection handler to report or suppress the error.
You are using setData after the response which causes the data to change and hence the useEffect(() => {<>your code<>} ,[data]) to fire again.
use useEffect(() => {<>your code<>},[]) if you want to execute the AJAX call only once after component mounting
or
use useEffect(() => {<>your code<>}) without the dependency if you want to execute the AJAX call after the component mount and after every update
Dependencies argument of useEffect is useEffect(callback, dependencies)
Let's explore side effects and runs:
Not provided: the side-effect runs after every rendering.
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs after EVERY rendering
});
}
An empty array []: the side-effect runs once after the initial rendering.
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs ONCE after initial rendering
}, []);
}
Has props or state values [prop1, prop2, ..., state1, state2]: the side-effect runs only when any dependency value changes.
import { useEffect, useState } from 'react';
function MyComponent({ prop }) {
const [state, setState] = useState('');
useEffect(() => {
// Runs ONCE after initial rendering
// and after every rendering ONLY IF `prop` or `state` changes
}, [prop, state]);
}

Infinite re-render in functional react component

I am trying to set the state of a variable "workspace", but when I console log the data I get an infinite loop. I am calling the axios "get" function inside of useEffect(), and console logging outside of this loop, so I don't know what is triggering all the re-renders. I have not found an answer to my specific problem in this question. Here's my code:
function WorkspaceDynamic({ match }) {
const [proposals, setProposals] = useState([{}]);
useEffect(() => {
getItems();
});
const getItems = async () => {
const proposalsList = await axios.get(
"http://localhost:5000/api/proposals"
);
setProposals(proposalsList.data);
};
const [workspace, setWorkspace] = useState({});
function findWorkspace() {
proposals.map((workspace) => {
if (workspace._id === match.params.id) {
setWorkspace(workspace);
}
});
}
Does anyone see what might be causing the re-render? Thanks!
The effect hook runs every render cycle, and one without a dependency array will execute its callback every render cycle. If the effect callback updates state, i.e. proposals, then another render cycle is enqueued, thus creating render looping.
If you want to only run effect once when the component mounts then use an empty dependency array.
useEffect(() => {
getItems();
}, []);
If you want it to only run at certain time, like if the match param updates, then include a dependency in the array.
useEffect(() => {
getItems();
}, [match]);
Your use of useEffect is not correct. If you do not include a dependency array, it gets called every time the component renders. As a result your useEffect is called which causes setProposals then it again causes useEffect to run and so on
try this
useEffect(() => {
getItems();
} , []); // an empty array means it will be called once only
I think it's the following: useEffect should have a second param [] to make sure it's executed only once. that is:
useEffect(() => {
getItems();
}, []);
otherwise setProposal will modify the state which will trigger a re-render, which will call useEffect, which will make the async call, which will setProposal, ...

Is secure to ignore the missing dependencies es-lint warning on react hooks?

I have a component which needs to call a function when is unmounted. This function is passed as a prop of the component. As far as I know, to call a function on component unmount you only need a useEffect with an empty dependency array:
useEffect(() => {
return () => { prop.onUnmount() }
}, []);
This works as expected, however ESLint complains about a missing dependency on the hook:
Line 156:6: React Hook useEffect has missing dependencies: 'props.onUnmount'. Either include them or remove the dependency array react-hooks/exhaustive-deps
However, neither solutions works,
If I remove the dependency array, the hook will be executed on each re render
If add the dependency, despite of prop.onUnmount() does not change on any rerender and the hook should not be triggered, it stills executing the return part. So the function gets called before the unmount.
So, is it safe to ignore the warning or is there any work around to execute the function and prevent ESLint to warn about it?
useEffect(() => {
return () => { prop.onUnmount() }
}, []);
This will run the prop.onUnmount function that existed on the very first render. So if that prop changes during the life of the component, your code will ignore that change, which is what the lint rule is warning you about.
If you want to run the prop.onUnmount that exists on the last render, then you'll need to do the following:
const cleanup = useRef();
// update the ref each render so if it changes the newest
// one will be used during unmount
cleanup.current = prop.onUnmount;
useEffect(() => {
return () => {
cleanup.current();
}
}, []);
If you find yourself doing this a lot, you may want to extract it to a custom hook, as in:
export const useUnmount = (fn) => {
const fnRef = useRef();
fnRef.current = fn;
useEffect(() => {
return () => {
fnRef.current();
}
}, []);
};
// used like:
useUnmount(props.onUnmount);

React useEffect hook missing dependencies linter warnings

I am using the React useEffect hook to obtain API data on component load, with the useAxios hook. The code is as below (simplified):
const [formData, setFormData] = useState<FormData>();
const [{ , executeGet] = useAxios('', {
manual: true,
});
const getFormData = async () => {
let r = await executeGet({ url: `http://blahblahblah/`});
return r.data;
};
useEffect(() => {
const getData = async () => {
try {
let response = await getAPIData();
if (response) {
setFormData(response);
} catch (e) {
setFormError(true);
}
};
getData();
}, []);
This pattern is used frequently in the codebase, but I am getting the linter warning:
React Hook useEffect has missing dependencies: 'getFormData'. Either include them or remove the dependency array react-hooks/exhaustive-deps
I can suppress the warning successfully with:
// eslint-disable-line react-hooks/exhaustive-deps
but it feels wrong to do this!
I can add constants to the dependency list without a problem, however when I add the getFormData function, I get an infinite loop. I have read around the area a lot and understand why the dependencies are needed. I am not sure if the useEffect hook is the best way to obtain the data, or whether there is a way to fetch data.
The problem is that you are defining getFormData within the component. In each render, it is reassigned. As is, this would mean that your initial useEffect would only be bound to to first getFormData, not the one from the most recent render. This causes a warning because often this is not what you intend, particularly if your getFormData depended on state or props that could change.
The simplest solution in this case is to move the definition of your getFormData outside of your component, and use Axios directly instead of using a hook. That way it wouldn't need to be defined on every render anyways.
you should initiate getFormData function using useCallback hook and then put it in useEffect dependency list.
const getFormData = useCallback(async () => {
let r = await executeGet({ url: `http://blahblahblah/`});
return r.data;
}, [executeGet]);
you can read more about useCallback in reactjs site:
https://reactjs.org/docs/hooks-reference.html#usecallback

Best practises React hooks HTTP loading

I recently started another project with react, as I had a little time to fiddle around, I used functional components with hooks. I had no problem whatsoever, there's just one thing I'm not sure I use correctly, here is an example :
function MyComponent() {
const [data, setData] = useState([]);
const [dataLoaded, setDataLoaded] = useState(false);
var getDataFromHTTP = async () { ... }
var loadData = async () => {
if (!dataLoaded) {
setDataLoaded(true);
setData(await getDataFromHTTP());
}
}
loadData();
return( ... );
}
If I like how everything is done, I suppose it's dirty to use loadData(); like in the preceding example, and I tried to useEffect with something like this :
useEffect(() => {
loadData();
}, []);
but then I got a warning like "loadData should be a dependency of useEffect". If I omit the the second argument to useEffect, it looks like it's the same as putting it directly in MyComponent. So basically, my question, in this example what is the best practise to load data once when the component is mounted ? and of course, when props/state change, what is the best practise to reload it if needed ?
EDIT:
The warning I have with useEffect is :
[Warning] ./src/list/main.js (1.chunk.js, line 25568)
Line 53: React Hook useEffect has a missing dependency: 'loadData'. Either include it or remove the dependency array react-hooks/exhaustive-deps
The way useEffect works is whenever something in the dependencies array change React will run that effect
useEffect(() => {
loadData();
}, [loadData]); // <-- dependencies array
But as you have declared loadData as a normal function it will get re-assigned to a new function on every render and it will trigger the effect.
Best way would be to wrap your loadData function in an useCallback hook
const loadData = useCallback(async () => {
if (!dataLoaded) {
setDataLoaded(true);
setData(await getDataFromHTTP());
}
}, [])

Resources