简体   繁体   English

通过React useEffect Hook更新状态时防止无限循环

[英]Prevent infinite loop when updating state via React useEffect Hook

I always seem to run into this problem, and I've never been able to wrap my head around it well enough to deal with it effectively. 我似乎总是遇到这个问题,而且我从来没有能够很好地解决它,以便有效地处理它。

I'll use an example based on the code block below, which represents a React functional component that uses useState and useEffect. 我将使用基于下面代码块的示例,它代表一个使用useState和useEffect的React功能组件。

Setup 设定

  1. There are some mp3 files located in an AWS S3 bucket. AWS S3存储桶中有一些mp3文件。 These have been processed so that the file names are in the format of "artist ||| title.mp3". 这些已经过处理,因此文件名的格式为“artist ||| title.mp3”。

  2. Metadata for those songs has been stored in a DyanamoDB table with a partition key of "artist" and a sort key of "title". 这些歌曲的元数据已存储在DyanamoDB表中,分区键为“artist”,排序键为“title”。

  3. There is a function, getS3songs, that asynchronously gets a list of all songs in the form of an array of objects, with a key, "key", that holds the file name from #2 above. 有一个函数getS3son​​gs,它以一个对象数组的形式异步获取所有歌曲的列表,其中包含一个键“key”,它保存上面#2的文件名。

  4. That same function runs a forEach on the file list and parses out "artist" and "title" from each file, then does a separate asynchronous API call to get the metadata for each song from a DynamoDB table via an API Gateway. 该函数在文件列表上运行forEach并从每个文件中解析出“artist”和“title”,然后执行单独的异步API调用,以通过API网关从DynamoDB表中获取每首歌曲的元数据。

  5. An array, "songs", is created in state, using a React useState hook. 使用React useState钩子在状态中创建数组“songs”。

What I'm trying to do What I'm ultimately trying to do is use a useEffect hook to populate the "songs" array with the metadata returned for each song from step #4. 我正在尝试做什么我最终要做的是使用useEffect钩子来填充“songs”数组,其中包含从步骤#4为每首歌曲返回的元数据。

The problem The following code block results in an infinite loop being run, as [songs] is set as second parameter of the useEffect hook. 问题以下代码块导致运行无限循环,因为[songs]被设置为useEffect挂钩的第二个参数。

I've tried several variations, but I believe the below represents the crux of the problem I'm trying to work through. 我尝试了几种变体,但我相信下面代表了我正在尝试解决的问题的症结所在。

Note The tricky part here isn't the initial get of the "s3Songs". 注意这里棘手的部分不是“s3Songs”的初始获取。 This could be put straight into state as a single object. 这可以作为单个对象直接进入状态。 The tricky part is that there are multiple asynchronous calls to an API to get the metadata for each file and getting each one of these objects into the "songs" array. 棘手的部分是,有多个异步调用API来获取每个文件的元数据,并将这些对象中的每一个都放入“歌曲”数组中。 And this is what's doing my head in. 而这就是我的目标。

Question What is the best or recommended pattern for this problem? 问题此问题的最佳或推荐模式是什么?

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;

Update Based on Will's comment regarding separating into two useEffect hooks. 更新根据Will关于分成两个useEffect挂钩的注释。 I have tried the below, and I'm thinking if I can work a Promise.all into the mix so as to return the songMetadata array in the second hook, after it's populated when all metadata promises have resolved, I'd be getting close. 我已经尝试了下面的内容,我正在考虑是否可以将Promise.all用于混合以便在第二个钩子中返回songMetadata数组,在所有metadata承诺解决后填充它时,我会越来越近。

  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]);

It looks like you need to separate the concerns of your two fetches by adding another useEffect . 看起来您需要通过添加另一个useEffect来分离两个提取的问题。 Then you can use Promise.all to wait for the responses from your second api call to complete before updating your songs. 然后,您可以使用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>
  );
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 useEffect - 更新状态时防止无限循环 - useEffect - Prevent infinite loop when updating state 设置 state 时防止 useEffect 中的无限循环 - Prevent infinite loop in useEffect when setting state 在使用 graphql 的 useQuery 获取数据的 useEffect 挂钩中更新 state 变量时防止无限渲染 - Prevent infinite renders when updating state variable inside useEffect hook with data fetched using useQuery of graphql react hook useEffect 无限循环 - react hook useEffect infinite loop react-native:当 api 响应更新为 state 时,在 useEffect 挂钩调用上,它将无限数组 Z34D1F91FB2E514B896BZA8 到控制台打印到无限循环 - react-native: On useEffect hook call when api response is updated to state it'll go to infinite loop but infinite array is printing in console React:在 useEffect 中使用自定义挂钩时出现无限循环? - React: Infinite loop when using custom hook inside of useEffect? 更新 state 并包含依赖数组时使用 Effect 无限循环 - useEffect infinite loop when updating state and including dependency array 反应 useEffect 钩子导致无限循环 - React useEffect hook is causing infinite loop 使用无限循环在 UseEffect 中反应钩子函数 - React Hook Function in UseEffect with infinite loop React:在 useEffect 中调用上下文函数时防止无限循环 - React: Prevent infinite Loop when calling context-functions in useEffect
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM