restrict image dimension in react-dropzone
Asked Answered
G

2

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";
};
Gerhard answered 16/1, 2020 at 1:41 Comment(0)
P
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)
})
Princessprinceton answered 16/1, 2020 at 2:34 Comment(6)
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.Gerhard
I see. You may have to restructure your function a little. Give me a moment, I will edit my answer with a solutionPrincessprinceton
please see my edit @Serenity, although you may need to restructure some of your original code.Princessprinceton
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) }Kaylenekayley
@aseladaskon that's a good catch, thanks. UpdatedPrincessprinceton
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 fileSelfabuse
H
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
    }

  });



Humeral answered 18/2, 2021 at 6:51 Comment(1)
thank you so much for taking the time to document this. Very helpful solution.Osithe

© 2022 - 2024 — McMap. All rights reserved.