0

I am writting a simple form for registring an account. The user should input his name, e-mail, password and a confirmation of his password.

My validates the password inside the onChange event in the input html. Here's my component:

import React, { useState } from 'react'
import Input from './Input'

const Register = () => {
  const [name, setName] = useState('')
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [pswdConfirm, setPswdConfirm] = useState('')

  function validatePassword() {
    setPasswordError('')
    setPswdConfirmError('')
    
    if (password.length < 6) {
      setPasswordError('Password must be at least 7 characters longs')
      return false
    }

    if (password !== pswdConfirm) {
      setPswdConfirmError('Passwords are not identical')
      return false
    }

    return true
  }

  const onSubmit = async (e) => {
    e.preventDefault();

    if (!validatePassword()) { // here it works
      return;
    }

  }

  return (
    <div className='d-flex justify-content-center align-items-center fullscreen'>
      <form className='h-50 d-block central-card container'>
        <Input 
          label='Fullname'
          type='text'
          onChange={(e) => setName(e.target.value)}
        />
        <Input 
          label='E-mail'
          type='text'
          onChange={(e) => setEmail(e.target.value)}
        />
        <Input 
          label='Password'
          type='password'
          error={passwordError}
          onChange={(e) => {
            setPassword(e.target.value)
            validatePassword() // here it doesnt
            console.log('password', password)
          }}
        />
        <Input 
          label='Confirm password'
          type='password'
          error={pswdConfirmError}
          onChange={(e) => {
            setPswdConfirm(e.target.value)
            validatePassword() // and neither here
            console.log('password confirmation', pswdConfirm)
          }}
        />

        <button className="w-100 row align-items-center" onClick={(e) => onSubmit(e)}>
          <span className="text-center">Sign-up</span>
        </button>
      </form>
    </div>
  )
}

export default Register

However, my setter from react hooks ('setPassword' and 'setPswdConfirm') do not seem to update my component state asynchronously which results in my 'onChange' valiation constantly failing, as the actual value in my component is always lagging one char behind the actual input. For example, when I type '1', my function will try to validate '', when I type '12' it will instead execute with '1', when I input '123', '12' and so on.

I was expecting my react-hook setter to syncrhously update my state so I could promptly valite the password. Am I missing something or not using this library appropriately?

I could instead run my valiation on e.target.value, however I'd like to understand react-hooks better and try for a more elegant solution before trying workarounds.Should I instead use 'useEffect'?

2 Answers 2

1

I think you are better off using useEffect to synchronise your validation. From react docs:

setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.

There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.

useEffect version:


import React, { useState, useEffect } from 'react'

const Input = (props) => <input {...props} />;

const Register = () => {
  const [name, setName] = useState('')
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [passwordError, setPasswordError] = useState('')
  const [pswdConfirm, setPswdConfirm] = useState('')
  const [pswdConfirmError, setPswdConfirmError] = useState('')

  const validatePassword = () => {
    setPasswordError('')
    setPswdConfirmError('')
    
    if (password.length < 6) {
      setPasswordError('Password must be at least 7 characters longs')
      return false
    }

    if (password !== pswdConfirm) {
      setPswdConfirmError('Passwords are not identical')
      return false
    }

    return true
  };

  useEffect(() => {
    validatePassword();
  }, [password, pswdConfirm]);

  const onSubmit = async (e) => {
    e.preventDefault();

    if (!validatePassword()) { // here it works
      return;
    }

  }

  return (
    <div className='d-flex justify-content-center align-items-center fullscreen'>
      <form className='h-50 d-block central-card container'>
        <Input 
          label='Fullname'
          type='text'
          onChange={(e) => setName(e.target.value)}
        />
        <Input 
          label='E-mail'
          type='text'
          onChange={(e) => setEmail(e.target.value)}
        />
        <Input 
          label='Password'
          type='password'
          error={passwordError}
          onChange={(e) => {
            setPassword(e.target.value)
            validatePassword() // here it doesnt
            console.log('password', password)
          }}
        />
        <p style={{ color: 'red' }}>{passwordError}</p>
        <Input 
          label='Confirm password'
          type='password'
          error={pswdConfirmError}
          onChange={(e) => {
            setPswdConfirm(e.target.value)
            validatePassword() // and neither here
            console.log('password confirmation', pswdConfirm)
          }}
        />
        <p style={{ color: 'red' }}>{pswdConfirmError}</p>

        <button className="w-100 row align-items-center" onClick={(e) => onSubmit(e)}>
          <span className="text-center">Sign-up</span>
        </button>
      </form>
    </div>
  )
}

export default Register
Sign up to request clarification or add additional context in comments.

Comments

1

You have to use the useEffect hook in order for the validation to be real time, the useEffect runs everytime the dependency changes (the dependency is the second argument which is an array), so I will modify your code like this and remove the validatePassword fn.

export default function App() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [pswdConfirm, setPswdConfirm] = useState("");
  const [passwordError, setPasswordError] = useState("");
  const [pswdConfirmError, setPswdConfirmError] = useState("");

  useEffect(() => {
    if (password.length < 6) {
      setPasswordError("Password must be at least 7 characters longs");
    } else {
      setPasswordError("");
    }

    if (password !== pswdConfirm) {
      setPswdConfirmError("Passwords are not identical");
    } else {
      setPswdConfirmError("");
    }
  }, [password, pswdConfirm]);

  const onSubmit = async (e) => {
    e.preventDefault();

    if (passwordError || pswdConfirmError) {
      return;
    }
  };

  return (
    <div className="d-flex justify-content-center align-items-center fullscreen">
      <form className="h-50 d-block central-card container">
        <input
          // label='Fullname'
          type="text"
          name={name}
          onChange={(e) => setName(e.target.value)}
        />
        <input
          // label='E-mail'
          type="text"
          name={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        <input
          // label='Password'
          type="password"
          name={password}
          error={passwordError}
          onChange={(e) => {
            setPassword(e.target.value);
            console.log("password", password);
          }}
        />
        <input
          // label='Confirm password'
          type="password"
          name={pswdConfirm}
          onChange={(e) => {
            setPswdConfirm(e.target.value);
          }}
        />
        {passwordError && <p>{passwordError}</p>}
        {pswdConfirmError && <p>{pswdConfirmError}</p>}
        <button
          className="w-100 row align-items-center"
          onClick={(e) => onSubmit(e)}
        >
          <span className="text-center">Sign-up</span>
        </button>
      </form>
    </div>
  );
}

Explanation: Every time the password or pswdConfirm changes the useEffect will run whatever is inside the function because they are included in the dependency list [password, pswdConfirm]. Aditionally I added the reset errors in an else statement, so you don't call it everytime the input changes.

While your validate password returns a boolean, this is not actually needed, because you can validate yourself using the passwordError and pswdConfirmError values in the onSubmit function like in the code I shared

  const onSubmit = async (e) => {
    e.preventDefault();

    if (passwordError  || pswdConfirmError) {
      return;
    }
    // Do the magic here
  }

The validatePassword will work but I think is redundant, so what you want to do in your effect is to modify the passwordError and pswdConfirmError. Please modify the code accordingly because I used regular inputs instead of the Input component you shared and remember to add the name prop to the inputs to match with the labels and improve a11y.

Here's the code sandbox in case you want to look how it looks: https://codesandbox.io/s/amazing-stallman-yd6g9h?file=/src/App.js

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.