简体   繁体   中英

useEffect on infinite loop using async fetch function

I am trying to understand why the following useEffect is running in an infinite loop. I made the fetchSchedule helper function to call the getSchedule service (using Axios to query the API endpoint). Reason I did not define this function inside the useEffect hook is because I would like to alternatively also call it whenever the onStatus function is invoked (which toggles a Boolean PUT request on a separate endpoint).

The eslinter is requiring fetchSchedule be added to the array of dependencies, which seems to be triggering the infinite loop.

The way it should work is fetching the data from the database on first render, and then only each time either the value prop is updated or the onStatus button is toggled.

So far my research seems to point that this may have something to do with the way useEffect behaves with async functions and closures. I'm still trying to understand Hooks and evidently there's something I'm not getting in my code…

import React, { useEffect, useCallback } from 'react';
import useStateRef from 'react-usestateref';
import { NavLink } from 'react-router-dom';
import { getSchedule, updateStatus } from '../../services/scheduleService';
import Status from './status';
// import Pagination from './pagination';

const List = ({ value }) => {
  // eslint-disable-next-line
  const [schedule, setSchedule, ref] = useStateRef([]);
  // const [schedule, setSchedule] = useState([]);

  const fetchSchedule = useCallback(async () => {
    const { data } = await getSchedule(value);
    setSchedule(data);
  }, [value, setSchedule]);

  const onStatus = (id) => {
    updateStatus(id);
    fetchSchedule();
    console.log('fetch', ref.current[0].completed);
  };

  useEffect(() => {
    fetchSchedule();
  }, [fetchSchedule]);
return (...)

Update March 2021

After working with the repo owner for react-usestateref , the package now functions as originally intended and is safe to use as a replacement for useState as of version 1.0.5 . The current implementation looks like this:

function useStateRef(defaultValue) {
  var [state, setState] = React.useState(defaultValue);
  var ref = React.useRef(state);

  var dispatch = React.useCallback(function(val) {
    ref.current = typeof val === "function" ?
    val(ref.current) : val;

    setState(ref.current);
  }, []);

  return [state, dispatch, ref];
};

You would be fine if it weren't for this react-usestateref import.

The hook returns a plain anonymous function for setting state which means that it will be recreated on every render - you cannot usefully include it in any dependency array as that too will be updated on every render. However, since the function is being returned from an unknown custom hook (and regardless, ESLint would correctly identify that it is not a proper setter function) you'll get warnings when you don't.

The 'problem' which it tries to solve is also going to introduce bad practice into your code - it's a pretty way to avoid properly handling dependencies which are there to make your code safer.

If you go back to a standard state hook I believe this code will work fine. Instead of trying to get a ref of the state in onStatus , make it async as well and return the data from fetchSchedule as well as setting it.

const [schedule, setSchedule] = useState([]);

const fetchSchedule = useCallback(async () => {
  const { data } = await getSchedule(value);
  setSchedule(data);
  return data;
}, [value]);

const onStatus = async (id) => {
  updateStatus(id);
  const data = await fetchSchedule();
};

useEffect(() => {
  fetchSchedule();
}, [fetchSchedule]);

Alternatively, although again I wouldn't really recommend using this, we could actually write a safe version of the useStateRef hook instead:

function useStateRef(defaultValue) {
    var [state, setState] = React.useState(defaultValue);

    var ref = React.useRef(defaultValue);
    ref.current = state; 

    return [state, setState, ref];
}

A state setter function is always referentially identical throughout the lifespan of a component so this can be included in a dependency array without causing the effect/callback to be recreated.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM