Forms ​
The app uses Formik for form state management and Yup for schema-based validation.
Form Components ​
Text Inputs ​
typescript
import {FormikTextInput, FormikPasswordInput} from 'src/design-system/containers/TextInput/TextInput';
import {FormikPhoneInput} from 'src/design-system/containers/FormikPhoneInput/FormikPhoneInput';
<FormikTextInput
name="email"
label="Email"
placeholder="Enter email"
testID="email-input"
isSensitiveInfo // Masks in Sentry
/>
<FormikPasswordInput
name="password"
label="Password"
/>
<FormikPhoneInput
name="telephone"
label="Phone number"
/>Dropdowns ​
typescript
import {FormikDropdownInput} from 'src/design-system/components/DropdownInputDS';
<FormikDropdownInput
name="category"
label="Category"
options={[
{label: 'Option 1', value: 'opt1'},
{label: 'Option 2', value: 'opt2'},
]}
isSearchable
/>Pickers (Radio/Checkbox) ​
typescript
import FormikPicker, {yesNoOptions, passFailOptions} from 'src/design-system/containers/FormikPicker/FormikPicker';
<FormikPicker
name="completed"
label="Is the task completed?"
options={yesNoOptions}
type="radioButton"
/>
<FormikPicker
name="checks"
label="Select all that apply"
options={customOptions}
type="checkbox"
/>Pre-built options: yesNoOptions, passFailOptions, passFailNAOptions, safeToUseOptions
Checkbox ​
typescript
import {FormikCheckbox} from 'src/design-system/containers/Checkbox/Checkbox';
<FormikCheckbox
name="agreeToTerms"
label="I agree to the terms"
/>Basic Form Pattern ​
typescript
import {Formik} from 'formik';
import * as Yup from 'yup';
import {FormikTextInput} from 'src/design-system/containers/TextInput/TextInput';
import {requiredString, phoneNumberValidationCustomerDetails} from 'src/utils/validations/formValidations';
const validationSchema = Yup.object().shape({
name: requiredString,
phone: phoneNumberValidationCustomerDetails,
});
const MyForm = () => {
const handleSubmit = (values) => {
console.log(values);
};
return (
<Formik
initialValues={{name: '', phone: ''}}
validationSchema={validationSchema}
onSubmit={handleSubmit}
validateOnMount
>
{({handleSubmit, isValid}) => (
<View>
<FormikTextInput name="name" label="Name" isSensitiveInfo />
<FormikTextInput name="phone" label="Phone" />
<Button onPress={handleSubmit} disabled={!isValid}>
Submit
</Button>
</View>
)}
</Formik>
);
};Validation ​
Pre-built Validators ​
From src/utils/validations/formValidations.ts:
typescript
import {
requiredField, // Basic required (treats '' as null)
requiredString, // String required
requiredRadioButton, // For picker selections
requiredToBeTrue, // Boolean must be true
phoneNumberValidationCustomerDetails, // UK phone with libphonenumber
postcodeValidation, // UK postcode
dateNotInThePast, // Date >= today
dateNotInTheFuture, // Date <= today
} from 'src/utils/validations/formValidations';Custom Validation ​
typescript
const validationSchema = Yup.object().shape({
email: Yup.string().email('Invalid email').required('Email is required'),
age: Yup.number().min(18, 'Must be 18 or older').required('Age is required'),
});Conditional Validation ​
Make fields required based on other field values:
typescript
const validationSchema = Yup.object().shape({
hasBoiler: requiredRadioButton,
// Only required if hasBoiler is 'Yes'
boilerType: Yup.mixed().when('hasBoiler', {
is: 'Yes',
then: () => requiredRadioButton,
}),
// Multiple conditions
boilerSerial: Yup.string().when(['hasBoiler', 'boilerType'], {
is: (hasBoiler, boilerType) => hasBoiler === 'Yes' && boilerType === 'Gas',
then: () => Yup.string().required('Serial number required'),
}),
});Text Input Validators ​
For multiline inputs with character limits:
typescript
import {
yupTextInputMultiline, // 10-500 chars
yupTextInputMultiline250, // 10-250 chars
yupTextInputMultilineNoMin, // 0-500 chars
} from 'src/utils/validations/inspectionReport/validationSchema';Keyboard Handling ​
Wrap forms in KeyboardAwareScrollView:
typescript
import {KeyboardAwareScrollView} from 'react-native-keyboard-controller';
<KeyboardAwareScrollView
keyboardShouldPersistTaps="handled"
bottomOffset={100} // Space for buttons
>
<Formik ...>
{/* form fields */}
</Formik>
</KeyboardAwareScrollView>Sensitive Data ​
Mark PII fields for Sentry masking:
typescript
<FormikTextInput
name="email"
label="Email"
isSensitiveInfo // Masked in error reports
/>Best Practices ​
- Use pre-built validators - Check
formValidations.tsbefore writing custom - Always add
testID- Required for E2E testing - Mark sensitive fields - Use
isSensitiveInfofor PII - Use
validateOnMount- Shows validation state immediately - Wrap in KeyboardAwareScrollView - Handles keyboard properly