Marcelo Potty
Marcelo Potty

Reputation: 354

Why is setState not working when using FileReader to read file content?

I'm making a file input to select multiple images that can be pre-visualized.

To handle multiple images I put them in an array of the component's state, and map the pre-visualized images.

But when I select the images from the input and set the state with this.setState({imgArray: newArray}), this.state.array.map(image=><img src={image}/>) doesn't re-render the selected images.

Code:

export default class posts extends Component {
  state = {
    fotos: ["/iconos/img.svg"] // If there are not img selected, it renders one image icon
  }

  onUploadVarious = e => {
    let newArray = []
    Object.values(e.target.files).map(file => {
      let nuevo = new FileReader()
      nuevo.onload = event=> newArray.push(event.target.result)
      nuevo.readAsDataURL(file)}
    )
    this.setState({fotos: newArray}) // the state is set correctly
  }
}

Render:

<div className=" border rounded" style={{"height":"30%", "overflow":"auto"}}>
    {this.state.fotos.map(foto =>
      <img src={foto||"/iconos/img.svg"}
        id="arch-preview"/>)} // it doesn't re-render when fotos state is changed
</div>

// input
<div className="mt-auto">
    <input multiple="multiple" type="file" 
    onChange={this.onUploadVarious} 
    className="form-control-file" name="file" />
</div>

Upvotes: 2

Views: 1274

Answers (1)

Ajeet Shah
Ajeet Shah

Reputation: 19813

FileReader reads file content asynchronously.

Due to this asynchronous nature, state is being set i.e. this.setState({fotos: newArray}) before data urls are set in newArray i.e. newArray.push(event.target.result).

And that's the reason your selected files aren't showing up.

To fix it, you can use create Promise which gets resolved after load event of each file. And use Promise.all which would be resolved after each Promise has resolved and then use setState:

readAsDataURL = (file) => {
  return new Promise((resolve, reject) => {
    const fr = new FileReader()
    fr.onerror = reject
    fr.onload = function () {
      resolve(fr.result)
    }
    fr.readAsDataURL(file)
  })
}

onUploadVarious = (e) => {
  Promise.all(Array.from(e.target.files).map(this.readAsDataURL))
    .then((urls) => {
      this.setState({ fotos: urls })
    })
    .catch((error) => {
      console.error(error)
    })
}

This has some good examples of Promises and their executing orders. Also check this about using FileReader with Promise.

Upvotes: 5

Related Questions