The function getImages is suppose to run once only on page load.
When i refresh the page the both tempQuestionImages and questionImages is empty. But when a hot reload is done it works perfectly.
I use nextJs and firebase,cloud storage if that helps
useEffect(() => {
async function getImages() {
await list(storageFolder, { maxResults: 10 }).then((res) => {
res.items.forEach((item) => {
tempQuestionImages.push(item.fullPath)
})
})
tempQuestionImages.forEach((url) => {
getDownloadURL(ref(firebaseStorage,url)).then((downUrl) => {
questionImages[questionImages.length] = downUrl
setQuestionImages([...questionImages])
})
setLoading(false)
})
}
getImages()
}, [])
any kind of help is much appriciated!
Update
So after following the example linked I did this.
useEffect(() => {
const storageFolder = ref(firebaseStorage,`/papers/${paper}`)
async function getImages() {
setTestList([])
const result = await (await list(storageFolder, { maxResults: 10 })).items.map((item) => { return [...item.fullPath] })
if (!ignore){
console.log(result)
setTestList(result)
}
}
let ignore = false
getImages()
return () => {
ignore = true
}
}, [])
But still i get the same result. result console logs as an empty array on refresh, but logs as an array with 6 items when hot reload.
5
Answers
Guess you should not keep the
!ignore
condition logic inside your async function (otherwise, it will be part of the same promise)Try the below code:
When you call for the information for tempQuestionImages and questionImages, it might have been processed after you are trying to use tempQuestionImages and questionImages. That’s why it is empty regularly and working during hot reload.
U should add a conditional statement at the start of your page.
This would make your page wait for data to have the image links before loading your page.
I had a similar issue and this fixed it.
Just put an ‘SVG’ with loading animation instead of a static span.
Or you could just put it on the image component, making it wait by flashing, youtube often does this if you refresh a page, it shows blank page with outlines, only when they have all the data do they show the video.
RCA
The async function
getImages
inside useEffect becomes a javascript closure.Hence you get the initial value of
tempQuestionImages
andquestionImages
as empty when a component mounts itself for the first time or when the app is reloaded manually..Whereas, hot reloading allows you to see the changes that you have made in the code without reloading your entire app. Therefore, it doesn’t re-mount the component, instead the component re-renders, henceforth, you have an access to the initial value during the re-render.
The code part
Never use, expensive functions in
useEffect
, becauseuseEffect
is mostly to run side-effects, logging, subscribing or pushing events, during the lifecycle of the component.If the code is not related to component lifecycle, it should not stay in the
useEffect
Few issues in the
useEffect
code.0. You are using
await
andthen
together. Please refer how promises work.map
. Expensive calculations, should not stay inside useEffect, as it is not related to the component lifecycle here.setState
inuseEffect
, setState itself is asynchronous. You can take a look on algorithm on how react update states using queuing mechanism hereRequirementNow as per your requirement, you need to have this called only once, but not on every re-render.
So to do this, create a singleton flag pattern in a different file.
Above code can be wrapped under
useMemo
. Just don’t need to create any extra tempQuestionImages variable. You can directly, see the below implementation of getImages. Tweak it accordingly, and there you go.Try console.log(storageFolder), I think that paper variable might be empty after first render. Also try to check network tab in dev tools to make sure that your request succeed. And also you might not want to declare functions in useEffect, I don’t think that this is good idea.
In case if you will find out that your paper variable doesn’t have expected value and as I understand this value wouldn’t change a lot, you can try something like this
Also I didn’t get what ignore exactly do here, cause it wouldn’t make a request on component dismount, it will just set a variable to true, and after that component mount again, it will became false again, so I removed it.
I also removed double await from your list request.
And one more thing, I remove
setTestList([])
if you need an array as a default value for this state, just set it inconst [testList, setTestList] = useState([])
.Hope it helps