HubertBlu
HubertBlu

Reputation: 819

How to show image upload previews with React?

I am building an image upload form using Next.js/React.js, and I want the user to be able to assign a tag to each upload. I also want to show the image preview using 'URL.createObjectURL'. The uploading works fine, but on the upload page where I try to iterate through the list of images to show the preview and show an input box to assign the tag, none of this is showing. I cannot work out why.

The code:

import { useState } from "react";
import Head from 'next/head'
import Layout, { siteTitle } from '../../components/layout'
import Image from 'next/image'

export default function PrivatePage(props) {
  const [images, setImages] = useState([])
  const [imageURLS, setImageURLS] = useState([])
  const [tag, setTag] = useState(null)

  const uploadImageToClient = (event) => {
    var imageList = images 
    var urlList = imageURLS
    if (event.target.files && event.target.files[0]) {
      imageList.push(event.target.files[0]);
      urlList.push(URL.createObjectURL(event.target.files[0]))
      setImages(imageList);
      setImageURLS(urlList);

    }

  };

  const uploadTagToClient = (event) => {
    if (event.target.value) {
      const i = event.target.value;
      setTag(i);
    }
  };

  const uploadToServer = async (event) => {
    const body = new FormData()
    images.map((file, index) => {
      body.append(`file${index}`, file);
    });
    body.append("tag", tag)
    const response = await fetch("/api/file", {
      method: "POST",
      body
    });
  };

  return (
    <Layout home>
    <Head>
      <title>{siteTitle}</title>
    </Head>

    <div className="container">
      
      <div className="row">
      <h4>Select Images</h4>
        <div className="col">
        
          <input type="file" className="btn btn-outline-success-inverse"  onChange={uploadImageToClient} />
          <input type="file" className="btn btn-outline-success-inverse" onChange={uploadImageToClient} />
 
        </div>
        <div className="col">
 
        </div>
        <button
            className="btn btn-outline-success-inverse"
            type="submit"
            onClick={uploadToServer}
          >
            Send to server
          </button>
          {images.map((file, index) => {
            return (
            <div class="row ">
              
              <lead>{file.name}</lead>
              <input type="text" onChange={uploadTagToClient} />
              <image src={imageURLS[index]}/>
            
            </div>)
          })}

      </div>
      
    </div> 
    
  </Layout>
    
  );
}

To clarify, nothing inside images.map is showing when I select images.

Upvotes: 5

Views: 10106

Answers (2)

Hitesh Chauhan
Hitesh Chauhan

Reputation: 1064

You can handle image upload with multiple image preview with following code.

const handleFile = (e) => {
    setMessage("");
    let file = e.target.files;
    
    for (let i = 0; i < file.length; i++) {
        const fileType = file[i]['type'];
        const validImageTypes = ['image/gif', 'image/jpeg', 'image/png'];
        if (validImageTypes.includes(fileType)) {
            setFile([...files,file[i]]);
        } else {
            setMessage("only images accepted");
        }   
    }  
}; 

Follow this snippet https://bbbootstrap.com/snippets/multiple-image-upload-preview-and-remove-92816546

Upvotes: 1

juliomalves
juliomalves

Reputation: 50228

The issue happens because you're mutating the arrays you have in state with imageList.push and urlList.push which React doesn't pick up. This means state doesn't actually get updated and a re-render doesn't occur.

To fix it, rather than mutating those state arrays you need to create new ones when updating them.

const uploadImageToClient = (event) => {
    if (event.target.files && event.target.files[0]) {
        setImages((imageList) => [...imageList, event.target.files[0]]);
        setImageURLS((urlList) => [
            ...urlList,
            URL.createObjectURL(event.target.files[0])
        ]);
    }
};

Unrelated to the main issue, you have several minor issues inside the render part of your component, specifically inside images.map.

  • You need to set a key prop on the outer <div> element;
  • The <lead> element doesn't exist, and needs to be replaced with a valid element;
  • The <image> element also doesn't exist, you probably meant <img> or <Image> (from next/image).

Upvotes: 3

Related Questions