skip to Main Content

I’m trying to download objects, store them in an array and save a copy of that array in state. I’m also trying to display content from the state array.

Here’s the component with the irrelevant parts excluded:

const UpdateProfile = () => {

  const [userFiles, setUserFiles] = useState<any>([])

  async function downloadFiles() {
    
    try {
      let newFileList: any = []
      const downloadedFiles = await listAll(storageRef)
      const downloadedFilesCopy = [...downloadedFiles.items]
      downloadedFilesCopy.forEach(async (file: any) => {
        let downloadedFileMetadata: any = await downloadMetadata(file)
        downloadedFileMetadata.url = await findDownloadURL(file)
        newFileList.push(downloadedFileMetadata)
      
      })
      console.log(newFileList)
      console.log(userFiles)
      setUserFiles((prev: any) => [...prev, ...newFileList])
      console.log(userFiles)
    } catch(e) {
      alert(e)
    }
  }

  async function downloadMetadata(item: StorageReference) {
    const fileRef = ref(storage, item.toString())
    try {
      const metadata = await getMetadata(fileRef)
      return metadata

    } catch(e) {
      alert(e)
    }

  }

  async function findDownloadURL(item: StorageReference) {
    const fileRef = ref(storage, item.toString())
    try {
      const url = await getDownloadURL(fileRef)
      return url

    } catch(e) {
      alert(e)
    } 
  }

  useEffect(() => {
    getProfileDetails()
    downloadFiles()

  }, [])
  
  return (
    <Container id="update-page-container">
        {userFiles.map((file: any) => <p>File here</p>)}
    </Container>
  )
}


The state is updated correctly in the background but there is no re-render, even though calling setUserFiles with a new array each time (as far as I can tell).

Can anyone help me get React to re-render because I’m pulling my hair out.

Tried multiple ways to set state, e.g.

      setUserFiles(newFileList)
      setUserFiles([...newFileList])

2

Answers


  1. Whats happening is that due to the asynchronous nature of your code (the download/for loop being async and the state update also being async, the setUserFiles might not be happening after the files have been downloaded. Specifically downloadedFilesCopy.forEach(async (file: any) ...

    So what’s probably happening is

    1. you are triggering the forloop and the async function calls
    2. The setUserFiles update gets called, but files aren’t done downloading.
    3. Then the files finish downloading, but setUserFiles has already executed with the old file list.

    I believe you can await Promise.all on the download portion of your code to wait for all files to finish downloading before updating the state variable.

    Something like

    const filesWithMetadata = await Promise.all(
        downloadedFilesCopy.map(async (file: any) => {
            const downloadedFileMetadata: any = await downloadMetadata(file);
            downloadedFileMetadata.url = await findDownloadURL(file);
              return downloadedFileMetadata;
            })
          );
    
    setUserFiles(filesWithMetadata)
    
    Login or Signup to reply.
  2. Functions which are used in a useEffect, should be declared within useEffect. So, the getProfileDetails should be inside in useEffect.

      useEffect(() => {
        async function downloadFiles() {
          // ...
          const newFileList = downloadedFilesCopy.map(async (file: any) => {
            let downloadedFileMetadata: any = await downloadMetadata(file)
            downloadedFileMetadata.url = await findDownloadURL(file) 
            return downloadedFileMetadata
          })
          setUserFiles((prev: any) => ([...prev, ...newFileList]))
          // ...
        }
    
        downloadFiles()
      }, [])
    

    Additionally, promises in Array.forEach don’t work as expected, may cause buggy results, instead, better use Array.map.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search