Skip to content

Forms

Forms are a common feature of any application. In this section, we will show you how you can handle form the right way with the starter.

react-hook-form

The starter uses react-hook-form to handle forms. It is a popular library that provides a lot of features out of the box. It is also very easy to use and integrate with React Native.

Make sure to check the react-hook-form documentation](https://react-hook-form.com/) to learn more about how to use it.

As we mention in the components section of the documentation here, we create a set of controlled components that are only used with react-hook-form. The starter only provides two components: ControlledInput and ControlledSelect but you can easily create other components using the same approach.

Here is the complete code of our ControlledInput when we use useController hook from react-hook-form to handle form state and validation rules:

src/ui/core/input/controlled-input.tsx
import * as React from 'react';
import type {
Control,
FieldValues,
Path,
RegisterOptions,
} from 'react-hook-form';
import { useController } from 'react-hook-form';
import type { NInputProps } from './input';
import { Input } from './input';
type TRule = Omit<
RegisterOptions,
'valueAsNumber' | 'valueAsDate' | 'setValueAs'
>;
export type RuleType<T> = { [name in keyof T]: TRule };
export type InputControllerType<T extends FieldValues> = {
name: Path<T>;
control: Control<T>;
rules?: TRule;
};
interface ControlledInputProps<T extends FieldValues>
extends NInputProps,
InputControllerType<T> {}
// only used with react-hook-form
export function ControlledInput<T extends FieldValues>(
props: ControlledInputProps<T>
) {
const { name, control, rules, ...inputProps } = props;
const { field, fieldState } = useController({ control, name, rules });
return (
<Input
ref={field.ref}
autoCapitalize="none"
onChangeText={field.onChange}
value={field.value as string}
{...inputProps}
error={fieldState.error?.message}
/>
);
}

If you want to create your own controlled component, you just need to make sure your component props type extends from InputControllerType the same way we are using it with ControlledInput. Here is another example of a Select input we create using the same approach as ControlledInput:

src/ui/core/select/controlled-select.tsx
import * as React from 'react';
import type { FieldValues } from 'react-hook-form';
import { useController } from 'react-hook-form';
import type { InputControllerType } from '../input';
import type { SelectProps } from './select';
import { Select } from './select';
interface ControlledSelectProps<T extends FieldValues>
extends SelectProps,
InputControllerType<T> {}
// only used with react-hook-form
export function ControlledSelect<T extends FieldValues>(
props: ControlledSelectProps<T>
) {
const { name, control, rules, ...selectProps } = props;
const { field, fieldState } = useController({ control, name, rules });
return (
<Select
onSelect={field.onChange}
value={field.value}
error={fieldState.error?.message}
{...selectProps}
/>
);
}

Use Case

Let’s say you want to create a form that allows the user to log in to the application. You will need to create a screen that contains the form with email and password fields, as well as a submit button. The form will need to be validated, and the data will need to be sent to the backend. Here’s how you can do it:

**Step 1: Create your schema validation **

The right way to validate a form is to create a schema validation. You can use any library you want but we recommend using zod as you can easily infer the types from the schema. Here is how you can create a schema validation for the login form:

import * as z from 'zod';
const schema = z.object({
email: z.string().email(),
password: z.string().min(6),
});
type FormType = z.infer<typeof schema>;

Step 2: Create your form component

Now that you have your schema validation, you can easily create your login screen using react-hook-form and the controlled components we already have. Here is how you can create your login screen:

import { zodResolver } from '@hookform/resolvers/zod';
import React from 'react';
import { useForm } from 'react-hook-form';
import * as z from 'zod';
import { useAuth } from '@/core';
import { Button, ControlledInput, View } from '@/ui';
const schema = z.object({
email: z.string().email(),
password: z.string().min(6),
});
type FormType = z.infer<typeof schema>;
export const Login = () => {
const { signIn } = useAuth();
const { handleSubmit, control } = useForm<FormType>({
resolver: zodResolver(schema),
});
const onSubmit = (data: FormType) => {
console.log(data);
signIn({ access: 'access-token', refresh: 'refresh-token' });
};
return (
<View className="flex-1 justify-center p-4">
<ControlledInput control={control} name="email" label="Email" />
<ControlledInput
control={control}
name="password"
label="Password"
placeholder="***"
secureTextEntry={true}
/>
<Button
label="Login"
onPress={handleSubmit(onSubmit)}
variant="primary"
/>
</View>
);
};

Done ! You have created a form with validation and typescript support.

Handling Keyboard

The template comes with react-native-avoid-softinput pre-installed and configured to handle the keyboard. You only need to use the useSoftKeyboardEffect hook on your screen, and you’re good to go.

import React from 'react';
import { useSoftKeyboardEffect } from '@/core/keyboard';
import { LoginForm } from './login-form';
export const Login = () => {
useSoftKeyboardEffect();
return <LoginForm />;
};