Search code examples
javascriptreactjsfilereader

ReactJS how do i save input file multiple image base64


Hi I am trying to save multiple image files base64 into state in array.

const uploadImage = e => {
    if (e.target.files && e.target.files.length > 0) {
        // ...
        let imagesArray = [];
        for (let i = 0; i < e.target.files.length; i++) {
            const image = e.target.files[i];
            const verified = verifyImage(image);

            if (verified) {
                const reader = new FileReader();
                const type = image.type;
                reader.addEventListener('load', () => {
                    let imgObj = {
                        base64: reader.result,
                        name: image.name,
                        type,
                        size: image.size,
                    };
                    imagesArray.push(imgObj)
                });
                reader.readAsDataURL(image);
            }
        }
        setImages(imagesArray);
    }

    e.target.value = null;
}

after updating state its not rendering in map function

{
                    images.length > 0
                    ?   images.map((imageObj, i) => {
                            return (
                                <div key={i}>
                                    <img
                                        src={imageObj.base64}
                                        alt=''
                                    />
                                    <div>
                                        <span>{imageObj.size ? imageObj.size : '-'}</span>
                                        <span>{imageObj.name ? imageObj.name : '-'}</span>
                                        <span
                                            onClick={() => removeImage(i)}
                                        >
                                            {translations(locale).remove}
                                        </span>
                                    </div>
                                </div>
                            )
                        })
                    :   null
                }

State array is updated but array.length is still showing as 0. What am i doing wrong ?


Solution

  • Do you have errors in the devtool console ? Got it working here https://stackblitz.com/edit/react-wdku9v

    EDIT:

    The issue here is that every time the uploadImage method is called it'll push an empty array.

    Why ?: when using FileReader you are attaching an event to it waiting it to load in an "async"? way. But your for loop do not wait so here is a description of what happens in the uploadImage.

    Given 1 image by the input :

    imagesArray is initialized as an empty array : []

    for loop start :

    1. create a new FileReader
    2. attach an event "load" to it
    3. read current image with reader.readAsDataURL(image)

    "load" event is not called yet

    for loop stop

    we change state with setImages(imagesArray) BUT, at this time imagesArray is still empty leading to a never showing image

    "load" event is called or later or before, we don't know.

    To prevent this from happening we can create another method that encapsulate the file reader in an Promise like so :

      const fileToDataUri = (image) => {
        return new Promise((res) => {
          const reader = new FileReader();
          const {type, name, size} = image;
          reader.addEventListener('load', () => {
              res({
                  base64: reader.result,
                  name: name,
                  type,
                  size: size,
              })
          });
          reader.readAsDataURL(image);
        })
      }
    

    and in the for loop we will concat all promises and await them in a Promise.all

            const newImagesPromises = []
            for (let i = 0; i < e.target.files.length; i++) {
                newImagesPromises.push(fileToDataUri(e.target.files[i]))
            }
            const newImages = await Promise.all(newImagesPromises)