Store validation status in redux state. This is bit complex on the redux side, but pretty simple on the components. It allows for reuse of the validation state instead of revalidating wherever needed.
155 lines
3.5 KiB
TypeScript
155 lines
3.5 KiB
TypeScript
import React, { useState } from 'react';
|
|
|
|
import {
|
|
HelperText,
|
|
HelperTextItem,
|
|
TextInput,
|
|
TextInputProps,
|
|
} from '@patternfly/react-core';
|
|
|
|
import { useAppDispatch, useAppSelector } from '../../store/hooks';
|
|
import {
|
|
setStepInputValidation,
|
|
selectInputValidation,
|
|
} from '../../store/wizardSlice';
|
|
|
|
interface ValidatedTextInputPropTypes extends TextInputProps {
|
|
dataTestId?: string | undefined;
|
|
ouiaId?: string;
|
|
ariaLabel: string | undefined;
|
|
helperText: string | undefined;
|
|
validator: (value: string | undefined) => boolean;
|
|
value: string;
|
|
placeholder?: string;
|
|
}
|
|
|
|
interface StateValidatedTextInputPropTypes extends TextInputProps {
|
|
dataTestId?: string | undefined;
|
|
ouiaId?: string;
|
|
stepId: string;
|
|
inputId: string;
|
|
ariaLabel: string | undefined;
|
|
helperText: string | undefined;
|
|
validator: (value: string | undefined) => boolean;
|
|
value: string;
|
|
placeholder?: string;
|
|
}
|
|
|
|
export const StateValidatedInput = ({
|
|
dataTestId,
|
|
ouiaId,
|
|
stepId,
|
|
inputId,
|
|
ariaLabel,
|
|
helperText,
|
|
validator,
|
|
value,
|
|
placeholder,
|
|
onChange,
|
|
}: StateValidatedTextInputPropTypes) => {
|
|
const dispatch = useAppDispatch();
|
|
const validatedState = useAppSelector(selectInputValidation(stepId, inputId));
|
|
const [isPristine, setIsPristine] = useState(!value ? true : false);
|
|
// Do not surface validation on pristine state components
|
|
const validated = isPristine ? 'default' : validatedState;
|
|
|
|
const handleBlur = () => {
|
|
setIsPristine(false);
|
|
const isValid = validator(value);
|
|
dispatch(
|
|
setStepInputValidation({
|
|
stepId,
|
|
inputId,
|
|
isValid,
|
|
errorText: isValid ? helperText : undefined,
|
|
})
|
|
);
|
|
};
|
|
|
|
const wrappedOnChange = (
|
|
evt: React.FormEvent<HTMLInputElement>,
|
|
newVal: string
|
|
) => {
|
|
if (onChange) onChange(evt, newVal);
|
|
const isValid = validator(newVal);
|
|
dispatch(
|
|
setStepInputValidation({
|
|
stepId,
|
|
inputId,
|
|
isValid,
|
|
errorText: isValid ? helperText : undefined,
|
|
})
|
|
);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<TextInput
|
|
value={value}
|
|
data-testid={dataTestId}
|
|
ouiaId={ouiaId}
|
|
type="text"
|
|
onChange={wrappedOnChange}
|
|
validated={validated}
|
|
aria-label={ariaLabel}
|
|
onBlur={handleBlur}
|
|
placeholder={placeholder}
|
|
/>
|
|
{validated === 'error' && (
|
|
<HelperText>
|
|
<HelperTextItem variant="error" hasIcon>
|
|
{helperText}
|
|
</HelperTextItem>
|
|
</HelperText>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export const ValidatedTextInput = ({
|
|
dataTestId,
|
|
ouiaId,
|
|
ariaLabel,
|
|
helperText,
|
|
validator,
|
|
value,
|
|
placeholder,
|
|
onChange,
|
|
}: ValidatedTextInputPropTypes) => {
|
|
const [isPristine, setIsPristine] = useState(!value ? true : false);
|
|
|
|
const handleBlur = () => {
|
|
setIsPristine(false);
|
|
};
|
|
|
|
const handleValidation = () => {
|
|
// Prevent premature validation during user's first entry
|
|
if (isPristine) {
|
|
return 'default';
|
|
}
|
|
return validator(value) ? 'success' : 'error';
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<TextInput
|
|
value={value}
|
|
data-testid={dataTestId}
|
|
ouiaId={ouiaId}
|
|
type="text"
|
|
onChange={onChange}
|
|
validated={handleValidation()}
|
|
aria-label={ariaLabel}
|
|
onBlur={handleBlur}
|
|
placeholder={placeholder}
|
|
/>
|
|
{!isPristine && !validator(value) && (
|
|
<HelperText>
|
|
<HelperTextItem variant="error" hasIcon>
|
|
{helperText}
|
|
</HelperTextItem>
|
|
</HelperText>
|
|
)}
|
|
</>
|
|
);
|
|
};
|