Search code examples
javascriptreactjsredux-form

Validate string length and make sure it contains certain characters in React Redux Form


Working on a set_password.js component that, you guessed it, lets the user set their password. It has them enter it twice to make sure they match. The password should have the criteria that it is at least 8 characters in length, has one uppercase, has one number, has one special character. Nothing ground breaking.

I know how to validate this stuff server side, but would rather validate it client side so the user doesn't have to wait for server response. I am struggling with how to do it.

I have a validate function that takes the values from the fields and validates them.

function validate(values) {
    const errors = {};

    if (!values.password1) {
        errors.password1 = 'This field cannot be empty.'
    }
    if (!values.password2) {
        errors.password2 = 'This field cannot be empty.'
    }
    if (values.password1.length < 8) {
         errors.password1 = 'The password provided is not long enough.'
    }
    if (values.password2.length < 8) {
         errors.password2 = 'The password provided is not long enough.'
    }
    return errors
}

This is all I have so far, and I get an error Cannot read property 'length' of undefined, so I am apparently putting this logic in the wrong area.

I'm starting to think that I should put it in the action so it is validated only after the user does onSubmit. I guess it would look something like:

onSubmit(values) {
    this.props.setPassword(values, this.props.match.params);  
}

export function setPassword({ password1, password2 }, { id, key }) {
    return function(dispatch) {
        if (password1.length < 8 && password2.length < 8) {
            axios
                .post(
                    `${ROOT_URL}/api/auth/set_password/`, 
                    { password1, password2, id, key }
                )
                .then(response => {
                    console.log('Password changed')
                })
                .catch(error => {        
                    dispatch(authError(error.response.data.non_field_errors[0]));
        });
        }

    }
}

Anyway, so what is the correct way to accomplish this? Checking length of string, that it has at least one uppercase letter, that is has one special character, and one number?

EDIT:

Relevant form data:

// This renders errors regarding the form inputs
    renderField(field) {
        const { 
            label, 
            type,
            name,
            meta: {
                touched, 
                error 
            } 
        } = field;

        return (
            <div className='form-group'>
                <label>{label}</label>
                <input 
                    className='form-control' 
                    type={type}
                    placeholder={label}
                    {...field.input} 
                />
                <div className='text-danger'>
                    {touched ? error : ""}
                </div>
            </div>
        );
    }

    // The main body to be rendered
    render() {
        if (this.props.permitRender) {
            const { handleSubmit } = this.props;
            return (
                <div>

                    <h3>Set New Password</h3>
                    <p>Type your new password twice. </p>
                    <p>It must be a minimum of 8 characters in length and contain at least:</p>
                    <ol>
                        <li>One uppercase character</li>
                        <li>One special character</li>
                        <li>One number</li>
                    </ol>

                    <form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
                        {this.renderAlert()}
                        <Field 
                            label='Enter New Password'
                            name='password1'
                            type='password'
                            component={this.renderField} 
                        />
                        <Field 
                            label='Confirm New Password'
                            name='password2'
                            type='password'
                            component={this.renderField} 
                        />
                        <button type="submit" className="btn btn-primary">Submit</button>
                    </form>
                </div>
            );            
        } else if (!this.props.permitRender) {
            return ( 
                <div> { this.renderAlert() } </div>
            );
        } 
    }

Solution

  • Right now, your validating if-statements are ordered somewhat wrongly. I don't think you can just assume any of the password values are set anyway.
    It'd probably be better to first do a null-check on the string you want to validate, and only after that do the length- and character check.

    The validation of what characters should be in there can probably be done by using one (or more, which is probably easier) regex patterns.

    // null check
    if (!values.password1) {
        errors.password1 = 'This field cannot be empty.'
    }
    // After null checking, check length
    else if (values.password1.length < 8) {
         errors.password1 = 'The password provided is not long enough.'
    }
    // Check for capital letters
    else if (/([A-Z]+)/g.test(values.password1) {
         errors.password1 = 'You don\'t have any capital letters in there yo'
    }
    
    // Same here as before, pretty much
    if (!values.password2) {
        errors.password2 = 'This field cannot be empty.'
    }
    else if (values.password2.length < 8) {
         errors.password2 = 'The password provided is not long enough.'
    }
    

    You can also do something like values.password1 && values.password1.length > 8 in your if statements to do each null check seperately.

    https://regex101.com/ is a good place to try out and experiment with regex patterns if you do go that route.