Update useTextField to support manually checking the validity of the TextField/TextArea
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