Search code examples
reactjsreact-hooks

Why is the useEffect changes to state in the Parent not happening in the Child?


Below is a FileUploader Component (parent) that renders ProgressBar's (children) as a file is added. I intend to pass in the FileReader onprogress so that the width of the progress bar goes from 0 to 100. To test I made a useEffect counter that goes from 0 to 100. I have confirmed that the counter state is updating by console.logging counter in the useEffect, however the progress state is not being changed in the ProgressBar component and only sends over the initial 0. How do I pass the counter state into the ProgressBar child so that it goes from 0 to 100?

import { useState, useCallback, useEffect } from 'react'
import { useDropzone } from 'react-dropzone'

const ProgressBar = ({ exists, file, progress }) => {
  // only the first value, 0, of progress comes through, why not through 100?
  console.log(progress)
  return (
    <div className='progress-bar-component'>
      <div className='progress-bar-filename'>
        <span>{ file.name }</span>
      </div>
      <div className='progress-bar-group'>
        <div
          className={ `progress-bar${exists.length ? ' progress-bar-exists' : ''}` }
          style={ { width: `${progress}%` } }
        >
        </div>
        <div className='progress-bar-percent'>
          <span>{ exists.length ? 'Already uploaded' : `${progress}%` }</span>
        </div>
      </div>
    </div>
  )
}

const ProgressBars = ({ progressBars }) => {
  return (
    <div className='progress-bar-container'>
      {progressBars}
    </div>
  )
}

const FileUploader = () => {
  const [filesUploaded, setFilesUploaded] = useState([])
  const [progressBars, setProgressBars] = useState([])
  //
  const [counter, setCounter] = useState(0)
  const [start, setStart] = useState(false)
  useEffect(() => {
    if (start) {
      const timer = counter < 100 &&
      setInterval(() => {
        setCounter(counter + 1)
      }, 500)
      return () => clearInterval(timer)
    }
  }, [counter, start])
  //
  const onDrop = useCallback(acceptedFiles => {
    acceptedFiles.forEach(file => {
      const reader = new FileReader()

      reader.onloadstart = () => {
        return setFilesUploaded(filesUploaded => {
          return [...filesUploaded, file]
        })
      }
      reader.onabort = () => console.log('file reading was aborted')
      reader.onerror = () => console.log('file reading has failed')
      reader.onprogress = () => {
        setStart(true)
        return setProgressBars(progressBars => {
          return [
            ...progressBars,
            <ProgressBar
              key={progressBars.length}
              file={file}
              exists={filesUploaded}
              progress={counter}
            />
          ]
        })
      }
      reader.onload = () => {
        
      }
      reader.readAsArrayBuffer(file)
    })
    
  }, [filesUploaded, setStart, counter])

  const { getRootProps, getInputProps } = useDropzone({ onDrop, multiple: true })

  return (
    <div>
      <div className='file-uploader'>
        <div
          className='file-uploader-input'
          {...getRootProps()}
        >
          <input {...getInputProps()} />
          <p>Upload Files</p>
        </div>
      </div>
      <ProgressBars progressBars={progressBars} />
    </div>
  )
}

export default FileUploader

Solution

  • You are calling inside a callback and the useEffect does not affect the rendering of that callback. Try calling outside the callback, in the return part of the parent component

    Something like this:

    return (
        <div>
          <div className='file-uploader'>
            <div
              className='file-uploader-input'
              {...getRootProps()}
            >
              <input {...getInputProps()} />
              <p>Upload Files</p>
            </div>
          </div>
          <ProgressBar
              key={progressBars.length}
              file={file}
              exists={filesUploaded}
              progress={counter}
            />
          <ProgressBars progressBars={progressBars} />
        </div>