How to validate an image in redux-form

by Pourya Dashtegolipour on Feb 7, 2019

A couple of weeks ago, we came across an issue when validating images using redux-form. Not only did we have to validate the format and size of the image file but also its width and height. Looking around the web there was no clear, well-explained solution to the problem, so… here’s one!

In this tutorial, we will learn how to validate both image format and the file size, width and height of an image. 

Limitations:

The main limitation we faced was as a result of all the elements stored in a file object, including: lastModified, lastModifiedDate, name, size, type, webkitRelativePath and of course __proto__. As you see, these are not enough for us to validate the width and height of an image.

Prerequisites: 🎨

  1. Create a react app.
  2. Add redux and redux-form as dependencies
  3. Connect redux and react
  4. Add semantic-ui-react to access its nice UI components [this step is optional].

Step 1:

First, let’s import all the necessary stuff at the top, such as ReactPropTypesFieldreduxFormButton and Form. As mentioned before, using Semantic UI is totally optional. In our Field we’ll have to pass our own component so that we can catch onChange events in the code below- it is written as this.renderFileInput and is explained later in this post.

Next, add the prop types and default props. And then a simple tiny form, a field and a submit button, as in the code below:

import React from "react";
import PropTypes from "prop-types";
import { Field, reduxForm } from "redux-form";
import { Button, Form } from "semantic-ui-react";
class SimpleForm extends React.Component {
    static propTypes = {
        previewLogoUrl: PropTypes.string,
        mimeType: PropTypes.string,
        maxWeight: PropTypes.number,
        maxWidth: PropTypes.number,
        maxHeight: PropTypes.number,
        // redux-form props
        handleSubmit: PropTypes.func.isRequired
    };
    static defaultProps = {
        previewLogoUrl: "https://imgplaceholder.com/400x300",
        mimeType: "image/jpeg, image/png",
        maxWeight: 100,
        maxWidth: 100,
        maxHeight: 100
    };
    render() {
        const {
            previewLogoUrl,
            maxWidth,
            maxHeight,
            maxWeight,
            handleSubmit
        } = this.props;
        return(
            <Form>
                <Form.Field>
                    <Field
                      name="image"
                      type="file"
                      validate={[
                        //////////////TODO///////////////
                        ///validation methods go here!///
                        //////////////TODO///////////////
                      ]}
                      component={this.renderFileInput}
                    />
                </Form.Field>
                <Button
                  primary
                  type="submit"
                  className="form-submit-button"
                  onClick={handleSubmit(this.handleSubmitForm)}
                >
                  Submit
                </Button>
            </Form>
        );
    }
}
export default reduxForm({
  form: "simple"
})(SimpleForm);

Step 2:

Let’s add those validation methods. First the easiest (file size); to do that just get the input file object and retrieve size from it. Then you can convert the size to kilobytes, megabytes or anything you want! Add your validation criteria and send an error message.

validateImageWeight = imageFile => {
    if (imageFile && imageFile.size) {
      // Get image size in kilobytes
      const imageFileKb = imageFile.size / 1024;
      const { maxWeight } = this.props;
      if (imageFileKb > maxWeight) {
        return `Image size must be less or equal to ${maxWeight}kb`;
      }
    }
  };

Next, file format; this one’s really simple too; to do that just retrieve type from file object and validate it. 😊 #lazywriting

validateImageFormat = imageFile => {
    if (imageFile) {
      const { mimeType } = this.props;
      if (!mimeType.includes(imageFile.type)) {
        return `Image mime type must be ${mimeType}`;
      }
    }
  };

Next, width and height. To achieve this, we’ll have to catch any onChangeevent from our redux field, construct a URL object (let’s call it imageObject) with our input file object (let’s call this imageFile), check image width and height from the URL object and finally store width and height in our input file object.

Field Component:

renderFileInput = ({ input, type, meta }) => {
    const { mimeType } = this.props;
    return (
        <div>
        <input
          name={input.name}
          type={type}
          accept={mimeType}
          onChange={event => this.handleChange(event, input)}
        />
        {meta && meta.invalid && meta.error && (
            <Message negative header="Error:" content={meta.error} />
        )}
        </div>
    );
};

Change Handler:

handleChange = (event, input) => {
    event.preventDefault();
    let imageFile = event.target.files[0];
    if (imageFile) {
        const localImageUrl = URL.createObjectURL(imageFile);
        const imageObject = new window.Image();
        imageObject.onload = () => {
            imageFile.width = imageObject.naturalWidth;
            imageFile.height = imageObject.naturalHeight;
            input.onChange(imageFile);
            URL.revokeObjectURL(imageFile);
        };
        imageObject.src = localImageUrl;
    }
};

Width and Height Validations:

validateImageWidth = imageFile => {
    if (imageFile) {
      const { maxWidth } = this.props;
      if (imageFile.width > maxWidth) {
        return `Image width must be less or equal to ${maxWidth}px`;
      }
    }
  };
  validateImageHeight = imageFile => {
    if (imageFile) {
      const { maxHeight } = this.props;
      if (imageFile.height > maxHeight) {
        return `Image height must be less or equal to ${maxHeight}px`;
      }
    }
  };

What happens now, once an image is selected by our input field, an onChangeevent is invoked, which in result will create a URL object from our image file, retrieve its width and height, sort them in the imageFile object and call the onChange once again but this time our imageFile object has width and height set in them! Next, the validation methods will be called by redux form validation just like a normal validation method. 

Code:

You can check the complete code below:

codesandbox

Hope someone out there finds this helpful.

Cheers!


 

The original version of this post can be found on Strands Tech Corner on Medium 

Get the latest updates here

SUBSCRIBE HERE