Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

useEffect props callback function causing infinite loop

I have a problem very similar to this - How do I fix missing dependency in React Hook useEffect.

There is one key difference - I am passing a fetch function to a child component to be called from useEffect, so I can't simply move the function into the body of the effect. The fetch function is re-created every render and causes an infinite loop. I have other local component state that I want to cause the effect to fire.

I basically have a Container Component and a Presentational component. MyPage is the parent of MyGrid and sets up all the redux state:

const MyPage = () => {

  const dispatch = useDispatch();
  const items= useSelector(selectors.getItems);
  const fetching = useSelector(selectors.getFetching);
  const fetchItems = opts => dispatch(actions.fetchItems(opts));

  return (
    <>
      {fetching && <div>Loading...</div>}
      <h1>Items</h1>
      <MyGrid
        items={items}
        fetchItems={fetchItems}
        fetching={fetching}
      />
    </>
  );

}

const MyGrid = ({ fetchItems, items, fetching }) => {

  const [skip, setSkip] = useState(0);
  const take = 100;
  const [sorts, setSorts] = useState([]);

  // when query opts change, get data
  useEffect(() => {

    const options = { skip, take };
    const sortString = getSortString(sorts);
    if (sortString) options['sort'] = sortString;
    fetchItems(options);

  }, [fetchItems, skip, sorts]);

In "MyGrid" "skip" and "sorts" can change, and should make the effect fire.

"fetchItems" is re-created everytime and causes an infinite loop. This is my problem.

Now, the eslint react-hooks/exhaustive-deps rule is making me put fetchItems in the dependency list. I have prettier setup to autofix on save which makes it worse.

I know the Container/Presentational pattern is out of style with hooks, but it works good for my situation - I may allow swapping out MyGrid for MyList dynamically and don't want to repeat all the redux stuff in each child component.

I tried to useCallback and useMemo, but eslint just makes me put all the same dependencies in it's dependency array parameter.

Is there a way other than disabling the eslint rule

// eslint-disable-next-line react-hooks/exhaustive-deps

to make this work?

like image 453
user210757 Avatar asked Jun 18 '19 22:06

user210757


People also ask

How do you stop an infinite loop using useEffect?

To get rid of your infinite loop, simply use an empty dependency array like so: const [count, setCount] = useState(0); //only update the value of 'count' when component is first mounted useEffect(() => { setCount((count) => count + 1); }, []); This will tell React to run useEffect on the first render.

Why does my useEffect keep running?

Changing state will always cause a re-render. By default, useEffect always runs after render has run. This means if you don't include a dependency array when using useEffect to fetch data, and use useState to display it, you will always trigger another render after useEffect runs.

Is useEffect a callback?

useEffect(callback, dependencies) is the hook that manages the side-effects in functional components. callback argument is a function to put the side-effect logic. dependencies is a list of dependencies of your side-effect: being props or state values.


1 Answers

There are two ways, you can make it work.

Firstly, using useCallback for fetchItem like

const fetchItems = useCallback(opts => dispatch(actions.fetchItems(opts)), [dispatch, actions]);

Secondly using dispatch directly in child component

const dispatch = useDispatch();
 useEffect(() => {

    const options = { skip, take };
    const sortString = getSortString(sorts);
    if (sortString) options['sort'] = sortString;
    dispatch(actions.fetchItems(options));

  }, [dispatch, actions, skip, sorts]);
like image 185
Shubham Khatri Avatar answered Sep 30 '22 03:09

Shubham Khatri