5
votes

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?

3
how is the getUsers function created and how is it passed to the component ?Gabriele Petrioli

3 Answers

10
votes

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.

0
votes

You should declare getUsers inside useEffect it it's the only place you call getUsers

useEffect(() => {
 const { getUsers } = props;
  getUsers();
}, []);
0
votes

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!