[英]How to prevent set state before useEffect?
I have a React/Next component.我有一个 React/Next 组件。 This component download data from firebase storage based on a route.
该组件根据路由从 firebase 存储中下载数据。 For example, for route http://localhost:3000/training/javascript the component with get data from /training/javascript router in firebase storage.
例如,对于路由 http://localhost:3000/training/javascript,组件从 firebase 存储中的 /training/javascript 路由器获取数据。
// ReactJS
import { useState, useEffect } from "react";
// NextJS
import { useRouter } from "next/router";
// Seo
import Seo from "../../../components/Seo";
// Hooks
import { withProtected } from "../../../hook/route";
// Components
import DashboardLayout from "../../../layouts/Dashboard";
// Firebase
import { getDownloadURL, getMetadata, listAll, ref } from "firebase/storage";
import { storage } from "../../../config/firebase";
// Utils
import prettysize from "prettysize";
import capitalize from "../../../utils/capitalize";
import { PlayIcon } from "@heroicons/react/outline";
import { async } from "@firebase/util";
function Video() {
// States
const [videos, setVideos] = useState([]);
// Routing
const router = useRouter();
const { id } = router.query;
// Reference
const reference = ref(storage, `training/${id}`);
useEffect(() => {
const fetchData = async () => {
let tempVideos = [];
let completeVideos = [];
const videos = await listAll(reference);
videos.items.forEach((video) => {
tempVideos.push(video);
});
tempVideos.forEach((video) => {
getMetadata(ref(storage, video.fullPath)).then((metadata) => {
completeVideos.push({
name: metadata.name,
size: prettysize(metadata.size),
});
});
});
tempVideos.forEach((video) => {
getDownloadURL(ref(storage, video.fullPath)).then((url) => {
completeVideos.forEach((completeVideo) => {
if (completeVideo.name === video.name) {
completeVideo.url = url;
}
});
});
});
setVideos(completeVideos);
};
fetchData();
}, [id]);
console.log("Render", videos)
return (
<>
<Seo
title={`${capitalize(id)} Training - Dashboard`}
description={`${capitalize(
id
)} training for all Every Benefits Agents.`}
/>
<DashboardLayout>
<h2>{capitalize(reference.name)}</h2>
<ul>
{videos.map((video) => {
return (
<li key={video.name}>
<a href={video.url} target="_blank" rel="noopener noreferrer">
<PlayIcon />
{video.name}
</a>
<span>{video.size}</span>
</li>
);
})}
</ul>
</DashboardLayout>
</>
);
}
export default withProtected(Video);
I have an useState that should be the array of videos from firebase. I use an useEffect to get the data from firebase and extract the needed information.我有一个 useState,它应该是来自 firebase 的视频数组。我使用 useEffect 从 firebase 获取数据并提取所需的信息。 Some medatada and the url.
一些 medatada 和 url。
Everything's fine.一切安好。 The information is extracted, and is updated to the state correctly.
信息被提取,并正确更新到 state。 But when the state is updated, it's no showings on the screen.
但是当state更新时,屏幕上没有任何显示。
This is a console.log of the videos state updated, so you can see it's correctly updated.这是视频 state 更新的控制台日志,因此您可以看到它已正确更新。
You messed up a bit with asynchronous code and loops, this should work for you:你搞砸了异步代码和循环,这应该适合你:
useEffect(() => { const fetchData = async () => { try { const videos = await listAll(reference); const completeVideos = await Promise.all( videos.items.map(async (video) => { const metadata = await getMetadata(ref(storage, video.fullPath)); const url = await getDownloadURL(ref(storage, video.fullPath)); return { name: metadata.name, size: prettysize(metadata.size), url, }; }) ); setVideos(completeVideos); } catch (e) { console.log(e); } }; fetchData(); }, []);
Promise.all
takes an array of promises, and returns a promise that resolves with an array of all the resolved values once all the promises are in the fulfilled
state. This is useful when you want to perform asynchronous operations like your getMetaData and getDownloadURL, on multiple elements of an array. Promise.all
接受一组承诺,并返回一个 promise,一旦所有承诺都已fulfilled
,它就会使用所有已解析值的数组进行解析 state。当您要执行异步操作(如 getMetaData 和 getDownloadURL)时,这很有用数组的多个元素。 You will use .map
instead of .forEach
since map returns an array, while forEach does not.您将使用
.map
而不是.forEach
,因为 map 返回一个数组,而 forEach 不返回。 By passing an async
function to .map
, since an async
function always returns a Promise
, you are basically creating an array of promises.通过将
async
function 传递给.map
,因为async
function 总是返回Promise
,您基本上是在创建一个承诺数组。 and that's what you can feed Promise.all
with.这就是你可以用来喂养
Promise.all
的东西。 That's it, now it just waits that all the async calls are done and you can just await
for Promise.all
to resolve.就是这样,现在它只是等待所有异步调用完成,您可以
await
Promise.all
来解决。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.