通过反应使用效果挂钩更新状态时,请防止无限循环



我似乎总是遇到这个问题,而且我从来没有能够很好地缠绕它来有效地处理它。

我将根据下面的代码块使用一个示例,该示例代表使用USESTATE和使用效率的React功能组件。

设置

  1. AWS S3存储桶中有一些MP3文件。这些已经处理过,以便文件名的格式为" Artist ||| Title.mp3"。

  2. 这些歌曲的元数据已存储在Dyanamodb桌子中,上面有"艺术家"的分区键和" title"的键。

  3. 有一个函数,get 3songs,异步地以一系列对象的形式获取所有歌曲的列表,并带有一个键"键",它在上面的#2中保存文件名。

  4. 同一函数在文件列表上运行forEach,并从每个文件中解析" Artist"one_answers" title",然后进行一个单独的异步API调用,以通过Dynamedodb表从Dynamphodb表中获取每首歌曲的元数据API网关。

  5. 一个数组"歌曲"是在状态下创建的。

我想做的我最终要做的是使用使用效果挂钩将"歌曲"数组填充"歌曲"阵列,从步骤4返回每首歌曲的元数据。

问题以下代码块会导致运行无限循环,因为[歌曲]被设置为使用效果挂钩的第二个参数。

我已经尝试了几种变体,但我相信以下代表了我试图解决的问题的症结。

注意这里的棘手部分不是" S3songs"的初始获得。这可以直接作为一个对象直接进入状态。棘手的部分是,对API有多个异步调用,以获取每个文件的元数据,并将这些对象中的每个对象都放入"歌曲"数组中。这就是我的头。

问题这个问题的最佳或推荐模式是什么?

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 基于Will关于分为两个使用效果钩的评论。我已经尝试了以下操作,并且我想如果我可以将Promise.all纳入混合物中,以便将Songmetadata阵列返回第二个挂钩,那么在所有metadata承诺都解决后填充后,我会靠近。<<<<<<<<<</p>

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

看起来您需要通过添加另一个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>
  );
};

相关内容

  • 没有找到相关文章