【问题标题】:Prevent infinite loop when updating state via React useEffect Hook通过 React useEffect Hook 更新状态时防止无限循环
【发布时间】:2019-11-11 21:10:03
【问题描述】:

我似乎总是遇到这个问题,而且我一直无法很好地解决这个问题来有效地处理它。

我将使用基于下面代码块的示例,它表示使用 useState 和 useEffect 的 React 功能组件。

设置

  1. 有一些 mp3 文件位于 AWS S3 存储桶中。这些已经过处理,文件名的格式为“艺术家||| title.mp3”。

  2. 这些歌曲的元数据已存储在 DyanamoDB 表中,分区键为“艺术家”,排序键为“标题”。

  3. 有一个函数,getS3son​​gs,它以对象数组的形式异步获取所有歌曲的列表,并带有一个键“key”,它保存上面 #2 中的文件名。

  4. 同样的函数在文件列表上运行 forEach 并从每个文件中解析出“艺术家”和“标题”,然后执行单独的异步 API 调用以从 DynamoDB 表中获取每首歌曲的元数据通过 API 网关。

  5. 使用 React useState 钩子在 state 中创建一个数组“songs”。

我要做什么 我最终想要做的是使用 useEffect 钩子来填充“songs”数组,其中包含从第 4 步返回的每首歌曲的元数据。

问题 以下代码块导致无限循环运行,因为 [songs] 设置为 useEffect 挂钩的第二个参数。

我尝试了几种变体,但我相信下面的内容代表了我正在尝试解决的问题的症结所在。

注意这里的棘手部分不是“s3Songs”的初始获取。这可以作为单个对象直接进入状态。棘手的部分是对 API 的多次异步调用以获取每个文件的元数据并将这些对象中的每一个都放入“songs”数组中。这就是我的想法。

问题 解决此问题的最佳或推荐模式是什么?

import React, { useEffect, useState } from "react";
import Amplify, { API, Storage } from "aws-amplify";
import awsconfig from "./aws-exports";

Amplify.configure(awsconfig);
const StackOverflowExample = () => {
  const [songs, setSongs] = useState([]);
  useEffect(() => {
    const getS3Songs = async () => {
      const s3Songs = await Storage.list("", { level: "public" });
      s3Songs.forEach(async song => {
        const artist = song.key.split(" ||| ")[0];
        const title = song.key.split(" ||| ")[1].slice(0, -4);
        const metadata = await API.get("SongList", `/songs/${artist}/${title}`);
        // setSongs([...songs, metadata]); <= causes loop
      });
    };
    getS3Songs();
  }, [songs]); // <= "songs" auto added by linter in create-react-app in vsCode.  Removing "songs" and disabling linter on that line doesn't help.

const renderSongs = () => {
  return songs.map(song => {
    return <li>{song.title}</li>;
  });
};

return (
  <div>
    <ul>{renderSongs()}</ul>
   </div>
  );
};

export default StackOverflowExample;

更新基于 Will 关于分成两个 useEffect 挂钩的评论。我已经尝试了下面的方法,我在想我是否可以在混合中使用Promise.all,以便在所有metadata 承诺都解决后填充第二个钩子中的 songMetadata 数组,我会越来越近了。

  useEffect(() => {
    console.log("Effect!");
    const getS3Files = async () => {
      try {
        const response = await Storage.list("", { level: "public" });
        setS3FileList(response);
      } catch (e) {
        console.log(e);
      }
    };
    getS3Files();
  }, []);

  useEffect(() => {
    console.log("Effect!");
    const songMetadata = [];
    s3FileList.forEach(async song => {
      const artist = song.key.split(" ||| ")[0];
      const title = song.key.split(" ||| ")[1].slice(0, -4);
      const metadata = await API.get(
        "SongList",
        `/songs/object/${artist}/${title}`
      );
      console.log(metadata);
      songMetadata.push(metadata);
    });
    setSongs(songMetadata);
  }, [s3FileList]);

【问题讨论】:

  • 尝试使 [songs] 像这样 [] 清空数组
  • 谢谢,@BradBall。是的,我已经尝试过了,但是一旦在 forEach 块中解决了每个承诺,就会导致“歌曲”被覆盖,这导致完成后“歌曲”数组中只剩下一首歌曲。

标签: reactjs amazon-dynamodb react-hooks aws-amplify


【解决方案1】:

您似乎需要通过添加另一个useEffect 来分离两个提取的关注点。然后您可以使用Promise.all 等待您的第二个 api 调用的响应完成,然后再更新您的歌曲。

const StackOverflowExample = () => {
  const [songs, setSongs] = useState([]);
  const [s3Songs, setS3Songs] = useState([]);
  const getSong = async song => {
    const artist = song.key.split(" ||| ")[0];
    const title = song.key.split(" ||| ")[1].slice(0, -4);
    const metadata = await API.get("SongList", `/songs/${artist}/${title}`);
    return metadata
  }
  useEffect(() => {
    const getS3Songs = async () => {
      const s3s = await Storage.list("", { level: "public" })n
       setS3Songs(s3s);
    };
    getS3Songs();
  }, []);
  useEffect(()=>{
     const pending = s3Songs.map(song=>getSong(song))
     Promise.all(pending).then(songs=>setSongs(songs));
  }, [s3Songs])
  const renderSongs = () => {
     return songs.map(song => {
       return <li>{song.title}</li>;
     });
};
return (
  <div>
    <ul>{renderSongs()}</ul>
   </div>
  );
};

【讨论】:

  • 谢谢威尔。我已经尝试过这种方法,它导致覆盖状态中的“歌曲”数组,而不是使用forEach 块中的每组元数据更新“歌曲”,我猜是因为关闭了forEach 循环中的“歌曲”?
  • 哇,威尔,这是一件艺术品!非常感谢您提供了如此令人难以置信的帮助并提供了如此有用的解决方案。
猜你喜欢
  • 2023-03-22
  • 2020-09-06
  • 2021-01-22
  • 2020-01-09
  • 2021-02-19
  • 2021-10-21
  • 2023-03-08
  • 1970-01-01
  • 2019-09-30
相关资源
最近更新 更多