[英]Prevent infinite loop when updating state via React useEffect Hook
我似乎總是遇到這個問題,而且我從來沒有能夠很好地解決它,以便有效地處理它。
我將使用基於下面代碼塊的示例,它代表一個使用useState和useEffect的React功能組件。
設定
AWS S3存儲桶中有一些mp3文件。 這些已經過處理,因此文件名的格式為“artist ||| title.mp3”。
這些歌曲的元數據已存儲在DyanamoDB表中,分區鍵為“artist”,排序鍵為“title”。
有一個函數getS3songs,它以一個對象數組的形式異步獲取所有歌曲的列表,其中包含一個鍵“key”,它保存上面#2的文件名。
該函數在文件列表上運行forEach
並從每個文件中解析出“artist”和“title”,然后執行單獨的異步API調用,以通過API網關從DynamoDB表中獲取每首歌曲的元數據。
使用React useState鈎子在狀態中創建數組“songs”。
我正在嘗試做什么我最終要做的是使用useEffect鈎子來填充“songs”數組,其中包含從步驟#4為每首歌曲返回的元數據。
問題以下代碼塊導致運行無限循環,因為[songs]被設置為useEffect掛鈎的第二個參數。
我嘗試了幾種變體,但我相信下面代表了我正在嘗試解決的問題的症結所在。
注意這里棘手的部分不是“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;
更新根據Will關於分成兩個useEffect掛鈎的注釋。 我已經嘗試了下面的內容,我正在考慮是否可以將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]);
看起來您需要通過添加另一個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>
);
};
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.