React Hook Form Fundamentals

Installing React Hook Form, Zod and the resolversShell
npx expo install react-hook-form zod @hookform/resolvers
Output
> npm install --save react-hook-form zod @hookform/resolvers
added 4 packages, changed 1 package, and audited 588 packages in 43s

useForm() returns control (the handle fields register with), handleSubmit, setError and formState. On the web you spread register('email') onto an <input>, and the library reads the DOM element's value. A TextInput has no DOM value, so each field registers through useController() (or <Controller>), which hands back value, onChange and onBlur to wire up. BookNest does that once, in a reusable Field:

src/checkout/Field.tsx: one labeled input wired to React Hook Form (excerpt)TSX
export function Field<T extends FieldValues>({ control, name, label, ...input }: Props<T>) {
  const { field, fieldState } = useController({ control, name });
  const labelId = `${name}-label`;
  return (
    <View>
      <Text style={ui.label} nativeID={labelId}>{label}</Text>
      <TextInput
        ref={field.ref}                          // lets handleSubmit focus the first error
        value={field.value}
        onChangeText={field.onChange}
        onBlur={field.onBlur}
        aria-labelledby={labelId}
        style={[ui.input, fieldState.error && { borderColor: '#c62828', borderWidth: 2 }]}
        {...input}
      />
      <Text style={ui.error} aria-live="polite">{fieldState.error?.message ?? ''}</Text>
    </View>
  );
}

Field is generic, so name must be one of the form's keys: name="emial" fails type checking with "Did you mean '"email"'?". In 7.88.0's source the defaults are mode: 'onSubmit', reValidateMode: 'onChange' and shouldFocusError: true: nothing complains until the first submit, then fields recheck per keystroke. 'onTouched' waits until a field loses focus, which suits phones.