0

I want to have a upload mechanism which uploads pictures and turns them into base64 strings and then save them to a state. After that, I want to map in in a component. But it turns out, that Array.map() returns a empty array in this situation. Does array.map has a limitation?

class TestFileInput extends React.Component {
  constructor(props){
        super(props);
    this.fileToBase64 = this.fileToBase64.bind(this);
    this.state = {
        examplePictures: undefined
    }
  }
  fileToBase64(files){
        let array = [];

        for(let i = 0; i < files.length; i++){
            let reader = new FileReader();
            let file = files[i];
            reader.onload = function(event) {
                // Push to array
                array.push(event.target.result)
            };
            reader.readAsDataURL(file);
        }
        //return array
        return array;
 }
  render() {
    console.log(this.state.examplePictures);
    return (
        <div>
          <input type="file" multiple onChange={(e) => {
            this.setState({
            examplePictures: this.fileToBase64(e.target.files)
          })
        }}/>
        {this.state.examplePictures !== undefined ? (
            <div>
              {this.state.examplePictures.map(pic => (
                <img src={pic} alt=""/>
                ))}
            </div>
        ):""}

      </div>
    );
  }
}

So basically, when you upload pictures. The fileToBase64 is getting called, returns an array (which works) with base64 strings. They get saved to the state (which works too). Only the mapping returns a empty array.

Here is a fiddle https://jsfiddle.net/op69hbxm/3/

6
  • array will be [], because you haven't chained the onLoad event. push will happen later after returning array Commented Sep 18, 2019 at 12:30
  • But the state has the values it needs, so the push event works. And I have reader.onload in there. Commented Sep 18, 2019 at 12:35
  • Try logging examplePictures, then mapping...{console.log(this.state.examplePictures);this.state.examplePictures.map()}. push works, but only after returning a empty array. Commented Sep 18, 2019 at 12:44
  • Yeah if I log it, examplepictures has the values it should have with the base64 strings. But if I map it, it just returns that empty array. Commented Sep 18, 2019 at 12:58
  • Where exactly? The first is for the callback and the second is for the map function Commented Sep 18, 2019 at 13:15

2 Answers 2

2

I think you missed the fact the FileReader performs an async operation. When you add exemplePictures to the state, the files are not done being converted.

I set up a codesandbox (https://codesandbox.io/s/clever-merkle-3qmwg) that, I guess, achieves what you want.

I did two things:

First, I promisify the fileReader to manipulate it more easily :

  readFileAsync = file => {
    return new Promise((resolve, reject) => {
      let reader = new FileReader();

      reader.onload = event => {
        resolve(event.target.result);
      };
      reader.onerror = reject;
      reader.readAsDataURL(file);
    });
  };

And I update the fileToBase64 function to use the previous function and WAIT that every files are read before updating the state:

  async fileToBase64(files) {
    const examplePictures = await Promise.all(
      Array.from(files).map(this.readFileAsync)
    );
    this.setState({ examplePictures });
  }

The Array.from(files) is just here to convert the FileList to Array and therefore be able to map the list of files easily`.

Sign up to request clarification or add additional context in comments.

Comments

0

I think the onLoad is asynchronous and somehow it's not adding to the state properly. I made a few modifications from your code where it's only add to the state after the image is loaded and also rendering only if it's have files

class TestFileInput extends React.Component {
  constructor(props){
        super(props);

    this.state = {
        examplePictures: []
    }
  }
   chooseFiles = (e) => {
     const files = e.target.files;
     let reader = new FileReader();
     reader.onload = (event) => {
       this.setState((previousState) => ({
         examplePictures: [
           ...previousState.examplePictures,
           event.target.result,
         ]
       }));
     };

     for(let i = 0; i < files.length; i++){
       let file = files[i];
       reader.readAsDataURL(file);
     }
   }
  render() {
    return (
        <div>
          <input type="file" multiple onChange={this.chooseFiles}/>
        {this.state.examplePictures.length > 0 && (
            <div>
            {this.state.examplePictures.map(pic => (
                <img src={pic} alt=""/>
            ))}
            </div>
        )}
      </div>
    );
  }
}

ReactDOM.render(
  <TestFileInput />,
  document.getElementById('container')
);

https://jsfiddle.net/x57aj9vt/28/

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.