Can I call separate function in useEffect?
The accepted answer is misleading in some way. Defining function inside component obviously re-creates on each render. But it does not mean that it will re-call in each render when using inside useEffect hook.
The key problem in your after saving file code, that you are watching for getData. As it is re-creating on each render, the useEffect accepts it as a change and thus cause you re-run on each render. The simple fix is not to watch getData.
But obviously, as suggested in the accepted answer. It's more good practice to separate the function outside the component so it won't re-create on each render.
Frankly, if I were you, I would not define function just for fetch:
useEffect(() => {
fetch(`https://jsonplaceholder.typicode.com/${query}`)
.then(response => response.json())
.then(json => setData(json));
}, [query]); // only re-run on query change
Since you declare the getData
function inside your React component it will be re-created on each render and thus the dependencies of your effect change on each render. This is the reason why the effect is executed on each render.
To prevent this you should declare the getData
function outside of the component and then pass query. Like so:
function getData(query) {
return fetch(`https://jsonplaceholder.typicode.com/${query}`)
.then(response => response.json());
}
function YouComponent({ query }) {
...
useEffect(() => {
getData(query).then(setData);
console.log("useEffect ran...");
}, [query]);
...
P.S: I'm not sure whether the eslint plugin will add getData to the dependencies automatically when doing it like this but even if it does so it doesn't hurt.
You can also use useRef
to create a ref and put the function definition there and call it using ref.current()
. Even React supports using ref as instance variables.
useRef
returns a mutable ref object whose.current
property is initialized to the passed argument (initialValue
). The returned object will persist for the full lifetime of the component.
However,
useRef()
is useful for more than theref
attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.
useEffect(() => {
getData.current()
console.log("useEffect ran...");
}, [query]);
const getData = useRef(() {
fetch(`https://jsonplaceholder.typicode.com/${query}`)
.then(response => response.json())
.then(json => setData(json));
});
Important:
If you want to use any state variable inside this method then you'll have to create a mutable object (like another ref) that will contain the latest value of state as if you directly refer to a state variable in that then that will be referring to the old/default value which was created at the time of method creation. To know more about that you can refer here. The same is true for useCallback
approach as well.
TL;DR
use useCallback()
First of all, letting a function becoming a dependency is very dangerous. If that function causes a state change, then the subsequent re-render will invoke the function again (through useEffect)... and an infinite loop begins.
One thing you can do, as many suggest here, is to create the function within the useEffect() method itself.
useEffect(() => {
function getData() {
fetch(`https://jsonplaceholder.typicode.com/${query}`)
.then(response => response.json())
.then(json => setData(json));
}
}
getData();
console.log("useEffect ran...");
}, [query]);
}
or simply
useEffect(() => {
(() => {
fetch(`https://jsonplaceholder.typicode.com/${query}`)
.then(response => response.json())
.then(json => setData(json)
)();
}, [query]);
}
Having said that, sometimes you want to still declare the function outside the useEffect for code-reuse. This time you need to make sure it is not re-created during each render. For that, you can either
declare the function outside the component -- which forces you to pass all variables as arguments.... which is a pain
wrap it inside
useMemo()
oruseCallback()
Here's an example
const getData = useCallback(()=>{...}, []);