I have a React/Next component. This component download data from firebase storage based on a route. For example, for route http://localhost:3000/training/javascript the component with get data from /training/javascript router in firebase storage.
// 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. Some medatada and the url.
Everything's fine. The information is extracted, and is updated to the state correctly. But when the state is updated, it's no showings on the screen.
This is a console.log of the videos state updated, so you can see it's correctly updated.
CodePudding user response:
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. You will use .map
instead of .forEach
since map returns an array, while forEach does not. By passing an async
function to .map
, since an async
function always returns a Promise
, you are basically creating an array of promises. and that's what you can feed Promise.all
with.
That's it, now it just waits that all the async calls are done and you can just await
for Promise.all
to resolve.