5

I am using react-dropzone for image upload. Everything is working fine. Validation for image size is also working fine. But I could not check the dimension for image. I want to validate the image's width and height to enforce user to upload the image in between those specified width and height. I tried image.addEventListener('load') but this is not working.

Here is what I have done

export const UploadField = ({ preview, label, uploadProps, ...props }) => {
  const {
    input: { onChange },
    disabled
  } = props;

  const {
    isDragActive,
    getRootProps,
    getInputProps,
    isDragReject,
    rejectedFiles
  } = useDropzone({
    onDrop: files => {
      onChange(
        files.map(file => {
          const image = new Image();
          image.addEventListener("load", () => {
            console.log("image", image);
          });
          return Object.assign(file, {
            preview: URL.createObjectURL(file)
          });
        })
      );
    },
    ...uploadProps
  });

  const isFileTooLarge =
    rejectedFiles.length > 0 && rejectedFiles[0].size > uploadProps.maxSize;

  const files = props.input.value;

  if (disabled) {
    return null;
  }

  return (
    <>
      {label && <Label>{label}</Label>}
      <DropzoneContainer {...getRootProps()}>
        <input {...getInputProps()} />
        {!isDragActive && "Click here or drop a file to upload!"}
        {isDragActive && !isDragReject && "Drop it like it's hot!"}
        {isDragReject && "File type not accepted, sorry!"}
        {isFileTooLarge && (
          <div className="text-danger mt-2">File is too large.</div>
        )}
      </DropzoneContainer>
      <div>
        {files && files !== undefined ? (
          <>
            <Preview files={files} isLocal />
          </>
        ) : (
          <Preview files={preview} isLocal={false} />
        )}
      </div>
    </>
  );
};

export default UploadField;

UploadField.defaultProps = {
  uploadProps: {
    accept: "image/*",
    multiple: false,
    minSize: 0,
    maxSize: 5242880
  }
};

const DropzoneContainer = styled.div`
  width: 100%;
  padding: 14px;
  border-width: 2px;
  border-radius: 2px;
  border-color: ${props => getColor(props)};
  border-style: dashed;
  background-color: #fafafa;
  color: #bdbdbd;
  outline: none;
  transition: border 0.24s ease-in-out;
`;

const getColor = props => {
  if (props.isDragAccept) {
    return "#00e676";
  }
  if (props.isDragReject) {
    return "#ff1744";
  }
  if (props.isDragActive) {
    return "#2196f3";
  }
  return "#eeeeee";
};
Serenity
  • 3,884
  • 6
  • 44
  • 87

2 Answers2

10

You never set the src for the image so your event handler never fires. Try setting image.src = URL.createObjectURL(file). Once the file loads, your 'load' handler will fire.

Try changing the contents of your onDrop callback to include this:

const filteredImages = [];
let counter = 0;

files.map(file => {
    const image = new Image();
    image.addEventListener('load', () => {
        console.log(`${image.width}x${image.height}`)

        // only select images within width/height limits
        if (image.width < WIDTH_LIM && image.height < HEIGHT_LIM) {
            filteredImages.push(image)
        }

        // increment counter for each image we go through
        counter += 1;

        // if we have gone through all the files, handle the ones that
        // made it through the filter using `handleImages` function
        if (counter === files.length) handleImages(filteredImages);
    });
    image.src = URL.createObjectURL(file)
})
kennyvh
  • 2,526
  • 1
  • 17
  • 26
  • 2
    this one worked. But problem is even if the uploaded image dimension does not match with the standard dimension that is mentioned in the condition, the image gets uploaded. – Serenity Jan 16 '20 at 04:22
  • I see. You may have to restructure your function a little. Give me a moment, I will edit my answer with a solution – kennyvh Jan 16 '20 at 04:25
  • please see my edit @Serenity, although you may need to restructure some of your original code. – kennyvh Jan 16 '20 at 04:38
  • this single & operator throwing an error in nextjs code base instead of that use two && ```if (image.width < WIDTH_LIM && image.height < HEIGHT_LIM) { filteredImages.push(image) }``` – asela daskon Feb 01 '22 at 02:06
  • @aseladaskon that's a good catch, thanks. Updated – kennyvh Feb 01 '22 at 20:38
  • I don't know why, but with this code, I can't call `removeFile(file)` function...I tried also with a `useEffect` boolean, It seems I can't remove the file – NineCattoRules Sep 04 '22 at 09:21
9

If you want to validate dimensions before onDrop, you can use getFilesFromEvent and validator callbacks like below.

Pros As with errors such as maxSize and accept, you can get files that are stuck in validation for dimention from rejectedFiles.

Cons f you are using typescript, you have to eliminate the type error with any type.

const {
    isDragActive,
    getRootProps,
    getInputProps,
    isDragReject,
    rejectedFiles
  } = useDropzone({
    getFilesFromEvent: async (event) => {
      const files = event.target.files || event.dataTransfer.files
      const promises = []
      for (let index = 0; index < files.length; index++) {
        const file = files[index]
        const promise = new Promise((resolve, reject) => {
          const image = new Image()
          let url: string
          image.onload = function () {
            file.width = image.width
            file.height = image.height
            resolve(file)
          }
          url = URL.createObjectURL(file)
          image.src = url
        })
        promises.push(promise)
      }
      return await Promise.all(promises)
    },
    validator: (file) => {
      // You can access width/height properties
      if(file.width < MIN_WIDTH) {
        return {
          code: "small-width",
          message: `Image width must be greater than ${MIN_WIDTH}`,
        }
      }
      return null
    }

  });



Community
  • 1
  • 1
nico
  • 403
  • 4
  • 10