Skip to content
New issue

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

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

Already on GitHub? # 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

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

No branches or pull requests

1 participant