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
emailValidation, // Email (stricter than Yup's lenient .email())
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: emailValidation.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