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:
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-formexport 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
:
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-formexport 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 />;};