John O'Brien
John O'Brien

Reputation: 186

Multiple image file upload only previewing first image in React

I've edited the original post as it had two separate problems and ultimately AKX in the comments helped me solve what the real problem was.

I'm trying to do an image preview for multiple images using appendChild as shown in the MDN docs, but when I go to submit the form and want to reset the page I'm noticing that using appendChild can be kind of an anti-pattern in React. I want to come up with a solution where I store an array of image tags that I could then just set to empty on submit to clear the page, as opposed to having to query select them all with document and remove.

With the code below I'm only rendering the first image out of a batch upload of multiple files. Single image uploads one after another work fine.

  const previewMainImages = (e) => {
    const files = Object.values(e.currentTarget.files)
    if (mainImageFiles.length + 1 > 10) {
      setErr(errMessage = 'Only 10 images can be uploaded here')
      return
    }
    
    function readAndPreview(file) {
      var reader = new FileReader();
      reader.onloadend = () => {
        var image = <img src={reader.result} alt={file.name}/>
        setMain(mainImageFiles = [...mainImageFiles, file])
        setMainImages(mainImages.concat(image))
      }
      reader.readAsDataURL(file);
    }

    if (files) {
      files.forEach((f, i) => {
        console.log(f)
        readAndPreview(f)
      });
    }
  }

Upvotes: 0

Views: 1452

Answers (1)

John O&#39;Brien
John O&#39;Brien

Reputation: 186

I'm still learning javascript and React so I forgot about useRef. Also based on AKX's advice I also changed from storing image html tags to storing image data objects that I then map over to display the image tags in the form.

There seemed to be a problem where I was using two setState's and this was preventing the .forEach loop from iterating over all imgObjs.

So at the top of my file I define my useRef:

let mainImages = useRef([]);

I tried to remove both setStates within reader.onloadend but I found that the second setState for storing image files was actually needed to cause a re-render to ultimately display the images.

const previewMainImages = (e) => {
    const files = Object.values(e.currentTarget.files)
    if (mainImageFiles.length + 1 > 10) {
      setErr(errMessage = 'Only 10 images can be uploaded here')
      return
    }
    
    const readAndPreview = (file) => {
      var reader = new FileReader();
      reader.onloadend = () => {
        var imgObj = {};
        imgObj.src = reader.result
        imgObj.alt = file.name
        mainImages.current.push(imgObj)
        setMain(mainImageFiles = [...mainImageFiles, file])
      }
      reader.readAsDataURL(file);
    }

    if (files) {
      files.forEach((f, i) => {
        readAndPreview(f)
      });
    } 
  }

And then in the relevant part of the html I'm simply doing this:

        <div
          className={'mainPreview'}
        >
          <h2>Main Images</h2>
          <p>{errMessage}</p>
          <input
            type='file'
            multiple
            name='image'
            accept='.png, .jpg, jpeg'
            onChange={e => previewMainImages(e)}
          />
          {mainImages.current.map((img, i) => {
            return <img key={i} src={img.src} alt={img.alt} />
          })}
        </div>

And then my reset inputs work like this:

  const resetInputs = () => {
    setMain(mainImageFiles = []);
    setBody(bodyImageFiles = []);
    mainImages.current = [];
    setDescription(description = '');
    setTag(tag = '');
    setTags(tags = []);
    setErr(errMessage = '');
  }

I'm still having a problem with the file input values not being reset but I'm going to leave that for another day.

Upvotes: 1

Related Questions