如何防止在 useEffect 之前设置状态?
How to prevent set state before useEffect?
我有一个 React/Next 组件。该组件根据路由从 firebase 存储下载数据。例如,对于路由 http://localhost:3000/training/javascript 从 /training/javascript 路由器在 firebase 存储中获取数据的组件。
// 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);
我有一个 useState,它应该是来自 firebase 的视频数组。我使用 useEffect 从 firebase 获取数据并提取所需的信息。一些 medatada 和 url.
一切都很好。信息被提取,并正确更新到状态。但是当状态更新时,屏幕上没有显示。
这是 console.log 视频状态更新,因此您可以看到它已正确更新。
你把异步代码和循环搞砸了,这应该对你有用:
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
接受一组承诺,并且 returns 一个承诺,一旦所有承诺都处于 fulfilled
状态,就会使用所有已解决值的数组来解决。当您想要对数组的多个元素执行异步操作(如 getMetaData 和 getDownloadURL)时,这很有用。您将使用 .map
而不是 .forEach
,因为 map returns 是一个数组,而 forEach 不是。通过将 async
函数传递给 .map
,因为 async
函数总是 returns 和 Promise
,您基本上是在创建一个承诺数组。这就是您可以用来喂养 Promise.all
的东西。
就是这样,现在它只是等待所有异步调用完成,你可以 await
等待 Promise.all
来解决。
我有一个 React/Next 组件。该组件根据路由从 firebase 存储下载数据。例如,对于路由 http://localhost:3000/training/javascript 从 /training/javascript 路由器在 firebase 存储中获取数据的组件。
// 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);
我有一个 useState,它应该是来自 firebase 的视频数组。我使用 useEffect 从 firebase 获取数据并提取所需的信息。一些 medatada 和 url.
一切都很好。信息被提取,并正确更新到状态。但是当状态更新时,屏幕上没有显示。
这是 console.log 视频状态更新,因此您可以看到它已正确更新。
你把异步代码和循环搞砸了,这应该对你有用:
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
接受一组承诺,并且 returns 一个承诺,一旦所有承诺都处于 fulfilled
状态,就会使用所有已解决值的数组来解决。当您想要对数组的多个元素执行异步操作(如 getMetaData 和 getDownloadURL)时,这很有用。您将使用 .map
而不是 .forEach
,因为 map returns 是一个数组,而 forEach 不是。通过将 async
函数传递给 .map
,因为 async
函数总是 returns 和 Promise
,您基本上是在创建一个承诺数组。这就是您可以用来喂养 Promise.all
的东西。
就是这样,现在它只是等待所有异步调用完成,你可以 await
等待 Promise.all
来解决。