Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update useTextField to support manually checking the validity of the TextField/TextArea #1361

Open
mlaursen opened this issue Feb 22, 2022 · 0 comments

Comments

@mlaursen
Copy link
Owner

mlaursen commented Feb 22, 2022

Is your feature request related to a problem? Please describe.
The main use case for this feature would be linking state together for multiple form parts. The best example is a "confirm password" field where you want to force a user to input the same password twice in two different fields.

Describe the solution you'd like
I think the easiest way to handle this would be just adding a new checkValidity() function to the third argument. It might also be useful to just implement a hook for useConfirmedPassword?

Describe alternatives you've considered
I tried hacking around with the setState function that is returned by the hook, but it's pretty messy. Here's an example:
https://codesandbox.io/s/form-example-confirm-password-hack-bpyrd9?file=/src/Demo.tsx

import { ReactElement, useRef } from "react";
import {
  defaultGetErrorMessage,
  Form,
  GetErrorMessage,
  PasswordWithMessage,
  TextContainer,
  useTextField,
} from "react-md";

const NO_MATCH = "Passwords do not match!";

export default function Demo(): ReactElement {
  const passwordRef = useRef<HTMLInputElement>(null);
  const confirmRef = useRef<HTMLInputElement>(null);

  const createGetErrorMessage =
    (isConfirm: boolean): GetErrorMessage =>
    (options) => {
      const { validity } = options;
      if (validity.valueMissing) {
        return "This value is required!";
      }

      if (validity.patternMismatch) {
        return "10+ characters, must have one lower case letter, one capital letter, one number, one special character, and no spaces.";
      }

      const currentPassword = isConfirm ? password : options.value;
      const currentConfirm = isConfirm ? options.value : confirmPassword;
      if (currentConfirm && currentConfirm !== currentPassword) {
        return NO_MATCH;
      }

      return defaultGetErrorMessage(options);
    };

  // Change pattern for your requirements.
  // This was https://stackoverflow.com/a/23711754
  const pattern = "^(?=.*\\d)(?=.*[A-Z])(?!.*[^a-zA-Z0-9@#$^+=])(.{8,15})$";

  const [password, passwordProps, { setState: setPasswordState }] =
    useTextField({
      id: "password",
      pattern,
      required: true,
      errorIcon: false,
      getErrorMessage: createGetErrorMessage(false),
      validateOnChange: true,
      onChange(event) {
        const { validity, validationMessage, value } = event.currentTarget;
        const errorMessage = createGetErrorMessage(false)({
          value,
          pattern,
          validity,
          validationMessage:
            validationMessage === NO_MATCH ? "" : validationMessage,
          validateOnChange: true,
          isBlurEvent: false,
        });

        setConfirmState((prevState) => {
          if (errorMessage === NO_MATCH) {
            return {
              ...prevState,
              error: true,
              errorMessage,
            };
          }

          if (prevState.errorMessage === NO_MATCH && !errorMessage) {
            confirmRef.current?.setCustomValidity("");
            return {
              ...prevState,
              error: false,
              errorMessage: "",
            };
          }

          return prevState;
        });
      },
    });
  const [confirmPassword, confirmPasswordProps, { setState: setConfirmState }] =
    useTextField({
      id: "confirm-password",
      pattern,
      required: true,
      errorIcon: false,
      getErrorMessage: createGetErrorMessage(true),
      validateOnChange: true,
      onChange(event) {
        const { validity, validationMessage, value } = event.currentTarget;
        const errorMessage = createGetErrorMessage(true)({
          value,
          pattern,
          validity,
          validationMessage:
            validationMessage === NO_MATCH ? "" : validationMessage,
          validateOnChange: true,
          isBlurEvent: false,
        });
        setPasswordState((prevState) => {
          if (errorMessage === NO_MATCH) {
            return {
              ...prevState,
              error: true,
              errorMessage,
            };
          }

          if (prevState.errorMessage === NO_MATCH && !errorMessage) {
            passwordRef.current?.setCustomValidity("");
            return {
              ...prevState,
              error: false,
              errorMessage: "",
            };
          }

          return prevState;
        });
      },
    });

  return (
    <TextContainer>
      <Form>
        <PasswordWithMessage
          {...passwordProps}
          ref={passwordRef}
          name="password"
          label="New Password"
        />
        <PasswordWithMessage
          {...confirmPasswordProps}
          ref={confirmRef}
          name="passwordConfirm"
          label="Confirm New Password"
        />
      </Form>
    </TextContainer>
  );
}

Additional context N/A

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant