Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I call separate function in useEffect?

Can I call another separate function in useEffect?

I am calling another function in useEffect but after saving the file it is automatically adding that function in array parameters of useEffect.

See the code below for proper understanding.

Before saving file:

useEffect(() => {
  getData()
  console.log("useEffect ran...");
}, [query]);

function getData() {
  fetch(`https://jsonplaceholder.typicode.com/${query}`)
  .then(response => response.json())
  .then(json => setData(json));
}

after saving file:

useEffect(() => {
  getData();
  console.log("useEffect ran...");
}, [getData, query]);

function getData() {
  fetch(`https://jsonplaceholder.typicode.com/${query}`)
    .then(response => response.json())
    .then(json => setData(json));
}

it is running again and again.

like image 984
Waseem Maya Avatar asked Jun 01 '19 21:06

Waseem Maya


People also ask

Can I call a function in useEffect?

The useEffect runs by default after every render of the component. When placing useEffect in our component we tell React that we want to run the callback as an effect. React will run the effect after rendering and after performing the DOM updates. If we pass only a callback, the callback will run after each render.

Can you have two components one useEffect?

You can have multiple useEffects in your code and this is completely fine! As hooks docs say, you should separate concerns. Multiple hooks rule also applies to useState - you can have multiple useState in one component to separate different part of the state, you don't have to build one complicated state object.

Can we pass async function in useEffect?

Either way, we're now safe to use async functions inside useEffect hooks. Now if/when you want to return a cleanup function, it will get called and we also keep useEffect nice and clean and free from race conditions. Enjoy using async functions with React's useEffect from here on out!

Can hook be called inside useEffect?

🔴 Do not call in event handlers. 🔴 Do not call Hooks inside functions passed to useMemo , useReducer , or useEffect .


4 Answers

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

  1. declare the function outside the component -- which forces you to pass all variables as arguments.... which is a pain

  2. wrap it inside useMemo() or useCallback()

Here's an example

    const getData = useCallback(()=>{...}, []);
like image 150
DanielK Avatar answered Oct 19 '22 08:10

DanielK


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.

like image 24
Sven Tschui Avatar answered Oct 19 '22 08:10

Sven Tschui


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
like image 7
Bhojendra Rauniyar Avatar answered Oct 19 '22 09:10

Bhojendra Rauniyar


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 the ref 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.

like image 2
Shivam Sharma Avatar answered Oct 19 '22 07:10

Shivam Sharma