使用 useEffect 时超出了最大深度

IT技术 javascript reactjs
2021-05-22 13:59:10

我正在尝试为我的产品 CRUD 实现一个简单的搜索算法。我想这样做的方法是在搜索栏中输入输入,每次用户更改输入时,与搜索匹配的产品都会立即出现,而无需点击搜索按钮。但是,我尝试这样做的方式是这样的:

function filterProducts (productName, productList) {
  const queryProducts = productList.filter((prod)=> {
    return prod.title === productName;
  });
  return queryProducts;
}

function HomePage () {
  const [productList, setProductList] = useState([]);
  const [popupTrigger, setPopupTrigger] = useState('');
  const [productDeleteId, setProductDeleteId] = useState('');
  const [queryString, setQueryString] = useState('');
  let history = useHistory();


  useEffect(() => {
    if (queryString.trim() === "") {
      Axios.get("http://localhost:3001/api/product/get-all").then((data) => {
        setProductList(data.data);
      });
      return;
    }
    const queryProducts = filterProducts(queryString, productList);
    setProductList(queryProducts);
  }, [queryString, productList]);

我知道productList每次渲染都会改变,这可能就是它不起作用的原因。但我不知道如何解决这个问题。我在这里看到了其他问题以及 useReducer 的解决方案,但我似乎没有一个对我有帮助。错误如下:

警告:已超出最大更新深度。当组件在 useEffect 中调用 setState 时会发生这种情况,但 useEffect 要么没有依赖项数组,要么依赖项之一在每次渲染时发生变化。

3个回答

您在这里所做的是获取产品列表并根据查询字符串对其进行过滤,然后使用过滤后的列表来呈现 UI。因此,理想情况下, yourfilteredList只是基于您的queryString的派生状态productList因此,您可以filterProducts从 useEffect 中删除并将其移到外面。这样它就会在状态发生变化时运行。

function filterProducts (productName = '', productList = []) {
  return productName.trim().length > 0 ? productList.filter((prod)=> {
    return prod.title === productName;
  }); : productList
}

function HomePage () {
  const [productList, setProductList] = useState([]);
  const [queryString, setQueryString] = useState('');
 


  useEffect(() => {
    if (queryString.trim() === "") {
      Axios.get("http://localhost:3001/api/product/get-all").then((data) => {
        setProductList(data.data);
      });
    }
  }, [queryString]);

  // query products is the derived state 
  const queryProducts = filterProducts(queryString, productList);

  // Now instead of using productList to render something use the queryProducts
  return (
    {queryProducts.map(() => {
      ..... 
    })}
  )

如果您希望filterProducts仅在更改时运行,queryString或者productList您可以将其包装在 useMemo 中

const queryProducts = React.useMemo(() => filterProducts(queryString, productList), [queryString, productList]);

当您在 useEffect 钩子中使用 setState 函数同时将该 setState 函数的状态作为 useEffect 钩子的依赖项之一时,您将获得这种递归效果,最终无限地重新渲染您的组件。

因此,首先我们必须从 useEffect 中删除 productList。然后,我们可以使用一个函数来更新您的状态而不是陈旧的更新(就像您在示例中所做的那样)。

function filterProducts (productName, productList) {
  const queryProducts = productList.filter((prod)=> {
    return prod.title === productName;
  });
  return queryProducts;
}

function HomePage () {
  const [productList, setProductList] = useState([]);
  const [popupTrigger, setPopupTrigger] = useState('');
  const [productDeleteId, setProductDeleteId] = useState('');
  const [queryString, setQueryString] = useState('');
  let history = useHistory();


  useEffect(() => {
    if (queryString.trim() === "") {
      Axios.get("http://localhost:3001/api/product/get-all").then((data) => {
        setProductList(data.data);
      });
      return;
    }

    setProductList(prevProductList => {
        return filterProducts(queryString, prevProductList)
    });
  }, [queryString]);

现在,您仍然可以访问您的过滤器的 productList,但您不必将它包含在您的依赖项中,它应该负责无限重新渲染。

我建议更改一些代码。

  1. 我会将始终立即反映用户输入的状态与表示发送到后端的查询的状态分开。我会在两个状态之间添加一个去抖动。像这样的东西:
const [query, setQuery] = useState('');
const [userInput, setUserInput] = useState('');
useDebounce(userInput, setQuery, 750);
  1. 我将拆分从后端返回的原始数据和刚刚从中派生的过滤数据
const [products, setProducts] = useState([]);
const [filteredProducts, setFilteredProducts] = useState([]);
  1. 我会拆分useEffect而不是将不同的关注点混合在一起(没有规定你不能有多个关注点useEffect
useEffect(() => {
    if (query.trim() === '') {
      Axios
        .get("http://localhost:3001/api/product/get-all")
        .then((data) => { setProducts(data.data) });
    }
  }, [query]);

useEffect(
    () => setFilteredProducts(filterProducts(userInput, products)),
    [userInput, products]
);