1

Folks, Am trying to only show the Button when the 2 form fields match. What am I missing in the logic? Current behavior is not predictable, and shows the button at random.

Kindly pardon the newb question, little front-end experience (if none at all)

Thanks!

class ResetPassword extends React.Component {
    constructor(props) {
         super(props);
         this.state = {};
         this.handleNewPasswordChange = this.handleNewPasswordChange.bind(this);
         this.handleConfirmPasswordChange = this.handleConfirmPasswordChange.bind(this);
         this.handleSubmit = this.handleSubmit.bind(this);
         this.checkPasswordMatch = this.checkPasswordMatch.bind(this);
    }

    handleNewPasswordChange(event) {
        this.setState({newPassword: event.target.value});
        this.checkPasswordMatch();
    }

    handleConfirmPasswordChange(event) {
        this.setState({confirmPassword: event.target.value});
        this.checkPasswordMatch();
    }

    checkPasswordMatch() {
        if (this.state.newPassword === this.state.confirmPassword && this.state.newPassword.length > 4) {
            this.setState({passwordsMatch: true});
        } else {
            this.setState({passwordsMatch: false});
        }
    }

    render() {
        return (
            <Container>
                <h1>
                    Password Reset
                </h1>
                <div className="login-box">
                    <form onSubmit={this.handleSubmit}>
                        <label>
                            {strings.NewPassword} : &nbsp;
                            <input
                                id="newPassword"
                                name="newPassword"
                                type="password"
                                value={this.state.newPassword}
                                onChange={this.handleNewPasswordChange} />
                        </label>
                        <label>
                            {strings.ConfirmPassword} : &nbsp;
                            <input
                                id="confirmPassword"
                                name="confirmPassword"
                                type="password"
                                value={this.state.confirmPassword}
                                onChange={this.handleConfirmPasswordChange} />
                        </label>
                        { this.state.passwordsMatch ? <Button>{strings.ChangePassword}</Button> : null }
                    </form>
                </div>
            </Container>
        )
    }
}
Cmag
  • 14,946
  • 25
  • 89
  • 140
  • The logic seems good to me, what do you mean by random? – norbitrial Dec 29 '19 at 19:02
  • @norbitrial well... the button is hidden at load time... which is great. But then when both form fields are filled out, and do in fact match, the button is never shown. only after more typing in fields does it finally show up.... The feeling is as if the state is updated either async or forceUpdate resets state to false? hooking up a console debug to this.... wish i could step through the application with a debugger :( ... – Cmag Dec 29 '19 at 19:03
  • Additionally: The condition is to have atleast 5 characters in the passwords to start showing the change button. Otherwise, the logic seems right to me. – Ayushya Dec 29 '19 at 19:07
  • Just removed the length check, and now something more interesting is happening. When the first form field is updated with 1 character, the button shows up :) . Overall feeling is as if the state is 1 step behind. – Cmag Dec 29 '19 at 19:09
  • Why do you need `this.forceUpdate();` when the state update already triggers a re-render? Your `this.forceUpdate();` is running even before the state is actually updated because of its async nature. – Akshit Mehra Dec 29 '19 at 19:23
  • OH! So setState is an asynchronous function in react? This makes total sense why its not working! So the question is... how do i await on the state update in the render() so the button will show up when forms fields match? – Cmag Dec 29 '19 at 19:24
  • 1
    You don't need to wait for that, every state update will cause a re-render. – Akshit Mehra Dec 29 '19 at 19:29
  • @AkshitMehra thank you for letting me know that setState is async . (helps a ton, as i do have context of asynchronicity ... at least on the backend) :) . So the main render() method.... Why doesnt it hide/show the button as expected? – Cmag Dec 29 '19 at 19:31
  • 1
    Found the problem, setState needs a callback. Submitting answer. Lol no wonder I hate front-end development. – Cmag Dec 29 '19 at 19:45
  • Maybe this will throw some light https://stackoverflow.com/a/53409805/7692076 – Akshit Mehra Dec 29 '19 at 20:18

2 Answers2

1

Remove the this.forceUpdate(); from the function below. It's not required because the state change already triggers a re-render.

this.forceUpdate(); runs even before your state is actually updated because of the asynchronous nature of the setState(), which in turn causes unexpected behaviour.

checkPasswordMatch() {
        if (this.state.newPassword === this.state.confirmPassword && this.state.newPassword.length > 4) {
            this.setState({passwordsMatch: true});
        } else {
            this.setState({passwordsMatch: false});
        }
        this.forceUpdate();  //Remove this
    }

Refer to @Cmag's answer for the further solution to issues caused by setState()'s async behaviour.

Akshit Mehra
  • 747
  • 5
  • 17
  • Have tried this... same effect. I believe the problem is with setState async nature as you've pointed out. Updating my question so it does not confuse people – Cmag Dec 29 '19 at 19:29
1

setState() is an async method. It accepts a callback function, which can be used to do further work.

Also, I have a suspicion that constantly re-rendering is a performance hit. In this case its acceptable.

    handleNewPasswordChange(event) {
        this.setState({newPassword: event.target.value}, this.checkPasswordMatch);
    }

    handleConfirmPasswordChange(event) {
        this.setState({confirmPassword: event.target.value}, this.checkPasswordMatch);
    }

    checkPasswordMatch() {
        if (this.state.newPassword === this.state.confirmPassword && this.state.newPassword.length>4) {
            this.setState({passwordsMatch: true});
        } else {
            this.setState({passwordsMatch: false});
        }
    }
    ```
Cmag
  • 14,946
  • 25
  • 89
  • 140