Stuiterbal
Stuiterbal

Reputation: 457

Compare form component values

I am trying to build a password confirmation form in React but I can't get the input validation to work both ways. To reproduce:

The last step does not work and I think there's something wrong with the way I'm setting the password state in handlePasswordInput. this.state.password in the isConfirmedPassword method does not hold the latest password when calling this method.

I've created a bin with the code: https://jsbin.com/sufupedayi/edit?js,output

Any React experts who can point me in the right direction?

SignIn = React.createClass({
    getInitialState() {
        return {
            password: null,
            confirmPassword: null
        }
    },
    handlePasswordInput(value) {
        if (!_.isEmpty(this.state.confirmPassword)) {
            this.refs.confirmPassword.validate(value);
        }

        this.setState({
            password: value
        })
    },
    handleConfirmPasswordInput(value) {
        this.setState({
            confirmPassword: value
        })
    },
    isConfirmedPassword(value) {
        return (value === this.state.password)

    },
    render() {
        return (
            <form autoComplete="off">
                <Input
                    name="password"
                    placeholder="Password"
                    errorMessage="Password is required"
                    onChange={this.handlePasswordInput}
                />
                <Input
                    ref="confirmPassword"
                    name="confirmPassword"
                    placeholder="Confirm password"
                    errorMessage="Passwords do not match"
                    onChange={this.handleConfirmPasswordInput}
                    validate={this.isConfirmedPassword}
                />
                <button type="submit">Submit</button>
            </form>
        )
    }
});

Input = React.createClass({
    getInitialState() {
        return {
            valid: false,
            value: null,
            errorMessage: this.props.errorMessage,
            errorVisible: false
        }
    },
    handleChange(event) {
        this.setState({
            value: event.target.value
        })

        if (this.props.validate) {
            this.validate(event.target.value);
        }

        if (this.props.onChange) {
            this.props.onChange(event.target.value);
        }

    },
    validate(value) {
        if (this.props.validate && this.props.validate(value)) {
            this.setState({
                valid: true,
                errorVisible: false
            });
        } else {
            this.setState({
                valid: false,
                errorVisible: true
            });
        }
    },
    render() {
        return (
            <div>
                <input
                    name={this.props.name}
                    placeholder={this.props.placeholder}
                    onChange={this.handleChange}
                />
                {!this.state.valid && <InputError errorMessage={this.state.errorMessage} visible={this.state.errorVisible} />}
            </div>
        )
    }
});

InputError = React.createClass({
    render() {
        var divStyle = {
            display: this.props.visible ? 'inline-block': 'none'
        }
        return (
            <div style={divStyle}>{this.props.errorMessage}</div>
        )
    }
})

React.render(<SignIn />, document.body);

Upvotes: 2

Views: 7053

Answers (2)

nanobar
nanobar

Reputation: 66425

I think you're making this more complicated than it needs to be and so you are already running into trouble with race conditions and hard to follow code. Try this instead:

var SignIn = React.createClass({
    getInitialState() {
        return {
            password: null,
            valid: false
        }
    },

    checkRequired(value) {
        return !!value.trim();
    },

    checkPasswordsMatch(value) {
        var match = this.refs.password.getValue() === value;
        this.setState({
            valid: match,
            password: value
        });
        return match;
    },

    render() {
        return (
            <form autoComplete="off">
                <Input
                    ref="password"
                    name="password"
                    placeholder="Password"
                    errorMessage="Password is required"
                    validate={this.checkRequired}
                />
                <Input
                    name="confirmPassword"
                    placeholder="Confirm password"
                    errorMessage="Passwords do not match"
                    validate={this.checkPasswordsMatch}
                />
                <button type="submit">Submit</button>
            </form>
        )
    }
});

var Input = React.createClass({
    getInitialState() {
        return {
            hasChanged: false,
            valid: false
        }
    },
    handleChange(event) {
        if (this.props.validate) {
            this.setState({
                valid: this.props.validate(event.target.value)
            });
        }

        this.setState({
            hasChanged: true
        });
    },

    getValue() {
        //return this.refs.input.value; // For React 0.14+
        return this.refs.input.getDOMNode().value;
    },

    render() {
        return (
            <div>
                <input
                    ref="input"
                    name={this.props.name}
                    placeholder={this.props.placeholder}
                    onChange={this.handleChange}
                />
                {this.state.hasChanged && !this.state.valid && <InputError errorMessage={this.props.errorMessage} />}
            </div>
        )
    }
});

var InputError = React.createClass({
    render() {
        return (
            <div>{this.props.errorMessage}</div>
        )
    }
})

e.g. https://jsbin.com/febagojayo/edit?js,output

Upvotes: 0

Bhargav Ponnapalli
Bhargav Ponnapalli

Reputation: 9422

Replace your handlePasswordInput with this. The reason it was happening was, your handlePasswordInput was being called before password was updated in the state. This does not happen when you were typing the confirm password field, because the password was already updated in the state.

Working fiddle - https://jsbin.com/cipuguxezi/1/edit?html,js,output

 handlePasswordInput(value) {
      this.setState({
         password: value
      });
      var self= this;
      window.setTimeout(function(){
        if (self.state.confirmPassword && self.state.confirmPassword.length) {
        self.refs.confirmPassword.validate(self.state.confirmPassword);
      }
    });

}

Note: There is some bug in JSBIN, please make sure

<div id="app"/>

is present above the script tag.

Upvotes: 2

Related Questions