Skip to content

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"
/>
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 ​

  1. Use pre-built validators - Check formValidations.ts before writing custom
  2. Always add testID - Required for E2E testing
  3. Mark sensitive fields - Use isSensitiveInfo for PII
  4. Use validateOnMount - Shows validation state immediately
  5. Wrap in KeyboardAwareScrollView - Handles keyboard properly