Kesley Fortunato Alves
Kesley Fortunato Alves

Reputation: 141

Can't set state on boolean property using setValues

i'm doing this pretty straight forward ReactJS login screen and there's a bug (at least i think so) in the setValue state. Basically, i set the default values for those values (as seen below)

const [values, setValues] = React.useState<State>({
        email: '',
        password: '',
        showPassword: false,
        stayLogged: false,
        openSnackBar: false,
        incorrectEmail: false,
        incorrectPassword: false,
        emailErrorMessage: '',
        passwordErrorMessage: ''
    });

I can change the other fields (including the openSnackBar), but not the incorrectEmail and incorrectPasswords fields. As you can see, in the snippet below i verify if the text matches the regex of a valid email address, if not, it should update the values with a new error message and true for incorrectEmail, but it doesn't. The same goes for incorrectPassword.

const verifyEmail = (email: React.ChangeEvent<HTMLInputElement>) => {
        let emailVerificationRegex = '^\\w+([-+.\']\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$';
        if ((!new RegExp(emailVerificationRegex).test(email.target.value)) && email.target.value !== '') {
            setValues({...values, incorrectEmail: true})
            setValues({...values, emailErrorMessage: 'The email you entered is invalid'})
            log('If email invalido: \nValor incorrectEmail = ' + values.incorrectEmail + '\nvalor emailErrorMessage ' + values.emailErrorMessage)
        } else {
            setValues({...values, incorrectEmail: false})
            setValues({...values, emailErrorMessage: ''})
            setValues({...values, email: email.target.value})
            console.log('If email valido: \nValor incorrectEmail = ' + values.incorrectEmail + '\nvalor emailErrorMessage ' + values.emailErrorMessage + '\nemail' + values.email)


        }
    }

What is causing this behaviour? How can i fix it? full snippet below

    import React from 'react';
import {createStyles, makeStyles, Theme} from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import {
    Button,
    FormControlLabel,
    Grid,
    IconButton,
    InputAdornment,
    Link,
    Snackbar,
    Switch,
    TextField
} from "@material-ui/core";
import {Close, Facebook, Visibility, VisibilityOff} from "@material-ui/icons";
import {log} from "util";

const useStyles = makeStyles((theme: Theme) =>
    createStyles({
        root: {
            maxWidth: 345,
        },
        buttonSpacing: {
            padding: theme.spacing(2)
        },
        marginSpacing: {
            margin: theme.spacing(1)
        },
        loginCardSpacing: {
            margin: theme.spacing(5)
        },
        forgotYourPassword: {
            paddingRight: 10
        },

    }),
);

interface State {
    email: string;
    password: string;
    showPassword: boolean;
    stayLogged: boolean;
    openSnackBar: boolean;
    incorrectEmail: boolean;
    incorrectPassword: boolean;
    emailErrorMessage: string;
    passwordErrorMessage: string;
}

export default function LoginCard() {
    const classes = useStyles();
    const [values, setValues] = React.useState<State>({
        email: '',
        password: '',
        showPassword: false,
        stayLogged: false,
        openSnackBar: false,
        incorrectEmail: false,
        incorrectPassword: false,
        emailErrorMessage: '',
        passwordErrorMessage: ''
    });
    const showHidePassword = () => {
        setValues({...values, showPassword: !values.showPassword})
    }
    const stayConnected = () => {
        setValues({...values, stayLogged: !values.stayLogged})
    }
    const handleCloseSnackBar = () => {
        setValues({...values, openSnackBar: !values.openSnackBar})
    }
    const verifyEmail = (email: React.ChangeEvent<HTMLInputElement>) => {
        let emailVerificationRegex = '^\\w+([-+.\']\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$';
        if ((!new RegExp(emailVerificationRegex).test(email.target.value)) && email.target.value !== '') {
            setValues({...values, incorrectEmail: true})
            setValues({...values, emailErrorMessage: 'The email you entered is invalid'})
            log('If email invalido: \nValor incorrectEmail = ' + values.incorrectEmail + '\nvalor emailErrorMessage ' + values.emailErrorMessage)
        } else {
            setValues({...values, incorrectEmail: false})
            setValues({...values, emailErrorMessage: ''})
            setValues({...values, email: email.target.value})
            console.log('If email valido: \nValor incorrectEmail = ' + values.incorrectEmail + '\nvalor emailErrorMessage ' + values.emailErrorMessage + '\nemail' + values.email)


        }
    }

    const verifyPassword = (password: React.ChangeEvent<HTMLInputElement>) => {
        if ((password.target.value.length < 5 || password.target.value.length > 24 || !password) && !(password.target.value === '')) {
            setValues({...values, incorrectPassword: true})
            setValues({...values, passwordErrorMessage: 'The password you entered is invalid'})
            console.log('If senha invalida: ' + values.incorrectPassword + ' ' + values.passwordErrorMessage)
        } else {
            setValues({...values, incorrectPassword: false})
            setValues({...values, passwordErrorMessage: ''})
            setValues({...values, password: password.target.value})
            console.log('else senha válida: ' + values.incorrectPassword + ' ' + values.passwordErrorMessage)

        }
    }

    function loginToApp(): any {
        if (!values.incorrectPassword || !values.incorrectEmail) {
            setValues({...values, openSnackBar: !values.openSnackBar})
        }
    }

    return (
        <div id="container" className={classes.loginCardSpacing}>
            <Grid
                container
                direction="row"
                justify="center"
                alignItems="center">
                <Grid>
                    <Card className={classes.root}>
                        <CardContent>
                            <form noValidate autoComplete="off">
                                <TextField id="loginField"
                                           label="Email"
                                           variant="filled"
                                           fullWidth
                                           margin="normal"
                                           error={values.incorrectEmail}
                                           onChange={verifyEmail}
                                           helperText={values.emailErrorMessage}/>
                                <TextField id="passwordField" label="Password"
                                           type={values.showPassword ? 'text' : 'password'}
                                           onChange={verifyPassword}
                                           variant="filled"
                                           fullWidth
                                           margin="normal"
                                           error={values.incorrectPassword}
                                           helperText={values.passwordErrorMessage}
                                           InputProps={{
                                               endAdornment: (
                                                   <InputAdornment position="end">
                                                       <IconButton
                                                           aria-label="toggle password visibility"
                                                           onClick={showHidePassword}
                                                       >
                                                           {values.showPassword ? <Visibility/> : <VisibilityOff/>}
                                                       </IconButton>
                                                   </InputAdornment>
                                               ),
                                           }}/>
                                <div id="buttonGroup">
                                    <Grid
                                        className={classes.marginSpacing}
                                        container
                                        direction="row"
                                        justify="space-around"
                                        alignItems="flex-start"
                                    >
                                        <FormControlLabel
                                            control={<Switch size="small" checked={values.stayLogged}
                                                             onChange={stayConnected} name="stayLoggedSwitch"/>}
                                            label="Stay Logged"
                                        />
                                        <Link className={classes.forgotYourPassword}>
                                            Forgot your password?
                                        </Link>
                                    </Grid>
                                    <Grid container
                                          direction="column"
                                          justify="center"
                                          alignItems="center"
                                          className={classes.buttonSpacing}>
                                        <Grid>
                                            <Button variant="contained" onClick={loginToApp}>
                                                Login
                                            </Button>
                                        </Grid>
                                        <Grid className={classes.marginSpacing}>
                                            <Button
                                                variant="contained"
                                                color="primary"
                                                startIcon={<Facebook/>}
                                            >
                                                Login with Facebook
                                            </Button>
                                        </Grid>
                                    </Grid>
                                </div>
                            </form>
                        </CardContent>
                    </Card>
                </Grid>
            </Grid>
            <Snackbar
                anchorOrigin={{
                    vertical: 'bottom',
                    horizontal: 'center',
                }}
                open={values.openSnackBar}
                autoHideDuration={6000}
                onClose={handleCloseSnackBar}
                message={values.passwordErrorMessage}
                action={
                    <React.Fragment>
                        <IconButton size="small" aria-label="close" color="inherit" onClick={handleCloseSnackBar}>
                            <Close fontSize="small"/>
                        </IconButton>
                    </React.Fragment>
                }
            />
        </div>
    );
}

Upvotes: 0

Views: 502

Answers (3)

josemartindev
josemartindev

Reputation: 1426

What I usually to in these situations and that I can recommend to you, is to create a clone of the whole state object, modify and update state afterwards, just like this example:

const verifyPassword = (password: React.ChangeEvent<HTMLInputElement>) => {
   if (condition) {
      let clone = {...values}; 
      clone.incorrectPassword = true; 
      setValues(clone);
   }
}

OR simply call setValues once including the corresponding changes:

setValues({ 
    ...values , 
    incorrectEmail: true , 
    emailErrorMessage: 'The email you entered is invalid'
})

Upvotes: 1

mortezashojaei
mortezashojaei

Reputation: 452

this is common mistake in react, and you can not change values state using values. you must replace

 setValues({...values, incorrectEmail: false})

with below code :

setValues(prevValues=>({...prevValues, incorrectEmail: false}))

and do this in your other setValues

Upvotes: 0

Mohammad Faisal
Mohammad Faisal

Reputation: 2363

You are calling multiple setState one after another . As a result the state is getting messed up. One possible solution is to use async await function. But i think for your issue simply updating state in one call is enough. Something like this.

setValues({ 
    ...values , 
    incorrectEmail: true , 
    emailErrorMessage: 'The email you entered is invalid'
})

Upvotes: 1

Related Questions