wsmd / react-use-form-state

📄 React hook for managing forms and inputs state

Home Page:http://react-use-form-state.now.sh

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Password confirmation use case questions

msakrejda opened this issue · comments

Thanks for the great library!

If I have a simple signup form:

const SignUp = ({onSignup}) => {
  const [ formState, {email, password}] = useFormState();

  const handleRegister = (e) => {
    e.preventDefault();
    onSignup(email, password);
  }

  return (
    <form action='post' onSubmit={handleRegister}>
      <label>e-mail:<input {...email('email')}/></label>
      <label>password:<input {...password('password')}/></label>
      <label>confirm password:<input {...password({
          name: 'confirm',
          validate: (value, values) => value === values.password || 'must match password'
        })}/></label>
      <input type='submit'>sign up</input>
    </form>
  )
}

This sort of works, but it doesn't behave quite how I want in a certain workflow:

  1. User enters email, password, matching password confirmation
  2. User changes their mind, and changes the password
  3. Password confirmation now does not match but is still valid (because the field value has not changed)

I know I could validate that the password matches the confirmation (in addition to validating that the confirmation matches the password), but that's not really what I want: if the user changes the password, it's clearly the confirmation that is now wrong, not the password.

I then realized I can change password to:

      <label>confirm password:<input {...password({
          name: 'password',
          onChange: (e) => {
            if (e.currentTarget.value !== formState.values.confirm) {
              formState.setFieldError('confirm', 'must match password');
            }
          }
          validate: (value, values) => value === values.password || 'must match password'
        })}/></label>

and then this works as expected, but it's not super elegant. Is there a better way to support this use case? Should there be a touch(field) method on formState to force re-validation (that would still need the custom handler, but at least avoid the duplicate validation logic)? Or a way to link fields (e.g., dependsOn: 'password' and re-run validation automatically)? Or do you think this is a niche use case and the current behavior is good enough?

@uhoh-itsmaciek I know it has been a while since this issue was created, but I hope you can still find this helpful! :)

I think this is a valid use case. However, re-running the validation of another input programmatically is not currently possible because input validation requires a change/blur event, so it can only happen when the user interacts with an input. In fact, the validate exposes the event that triggers the validation as well.

if (isFunction(inputOptions.validate)) {
const result = inputOptions.validate(value, values, e);

Introduce the ability to programmatically re-validating exiting inputs against their current values without an event is a breaking change. I'll take this into consideration in future major releases.

For the time being, as you know, the form state can be manually updated using dedicated methods such as setFieldError. I think useEffect is the prefect candidate here to achieve the fields "dependency" behavior you mentioned above without any code duplications or validate functions.

const Form = () => {
  const [formState, { password }] = useFormState();

  // ensures that both `password` and `confirm` are always in sync
  // whenever either one changes
  useEffect(() => {
    const { password, confirm } = formState.values;
    formState.setFieldError(
      'confirm',
      password !== confirm ? 'password must match' : undefined,
    );
  }, [formState.values.password, formState.values.confirm]);

  return (
    <form>
      <label>
        password: <input {...password('password')} />
      </label>
      <label>
        confirm password: <input {...password('confirm')} />
      </label>
    </form>
  );
};

Cool, thanks for responding. After some reflection, I think this is probably reasonable to keep the surface area of the library simple.