Componentes TextInput do React Native são sempre controlados pelo lado nativo, mas você ainda quer um estado de formulário não controlado no thread JS. react-hook-form se inscreve em mudanças de campo em vez de re-renderizar a tela inteira a cada pressionamento de tecla - o padrão que mantém fluxos de cadastro e checkout com múltiplos campos fluidos em dispositivos Android de gama média.
useForm mantém os valores dos campos em um armazenamento baseado em ref - atualizar um campo não re-renderiza o pai, a menos que você se inscreva nessa fatia do formState.
Controller registra o campo, conecta onChange a onChangeText, e anexa o ref para que setFocus possa chamar TextInput.focus().
A validação é feita através de rules no Controller, um resolver customizado, ou zodResolver (veja Validação com Zod).
handleSubmit executa a validação primeiro; seu onSubmit só é acionado quando o formulário é válido.
formState usa um Proxy - ler errors.email inscreve o componente nos erros de email; ler todo o objeto formState se inscreve em tudo.
Espalhar register em TextInput - register retorna props centradas na web (onChange, name) que não mapeiam para onChangeText. Correção: Use Controller e mapeie onChange → onChangeText.
Chaves defaultValues ausentes - Novos campos mostram undefined, isDirty permanece falso após edições, e reset() os ignora. Correção: Liste todos os campos em defaultValues, incluindo strings vazias e arrays vazios.
Desestruturar todo o formState - const { formState } = useForm() e depois ler formState.isSubmitting no render ainda funciona, mas const { isSubmitting, errors, isDirty, isValid } = formState se inscreve em quatro sinais de uma vez. Correção: Desestruture apenas o que o componente exibe.
watch() no render pai - watch() sem argumentos re-renderiza a cada pressionamento de tecla em todos os campos - o problema que RHF resolve. Correção:watch("email") para um campo, ou mova a UI observada para um filho isolado.
Valor undefined do TextInput - Passar value={undefined} aciona avisos e limpa a posição do cursor. Correção:value={value ?? ""} no render do Controller.
onSubmit sem handleSubmit - Pressionar o botão pula a validação e envia dados parciais. Correção:onPress={handleSubmit(onSubmit)} no Pressable, nunca onPress={onSubmit}.
Erros do servidor não mapeados para campos - A API retorna { fieldErrors: { email: "Já em uso" } } mas a UI mostra um alerta genérico. Correção: Use setError("email", { type: "server", message }) e setFocus("email").
setFocus é embutido no useForm - nenhum mapeamento manual de ref necessário quando os nomes dos campos são registrados.
Defina returnKeyType="done" no último campo e chame handleSubmit(onSubmit) em onSubmitEditing.
Como foco o primeiro campo inválido após o submit?
handleSubmit(onSubmit, (invalid) => { const first = Object.keys(invalid)[0] as keyof RegisterValues; setFocus(first);})();
O segundo argumento de handleSubmit é executado quando a validação falha.
Combine com scrollTo de um ref em KeyboardAwareScrollView para formulários longos.
react-hook-form funciona com TypeScript no Expo?
Sim - passe um genérico para useForm<FormValues>() e para Control<FormValues> em componentes de campo.
Combine com zodResolver e z.infer<typeof Schema> para formulários totalmente tipados.
Instale via npx expo install react-hook-form @hookform/resolvers zod.
Como mostro um estado de carregamento no botão de submit?
Desestruture isSubmitting de formState.
handleSubmit o define como true enquanto sua promise onSubmit assíncrona está pendente.
Desabilite o Pressable e mude o texto do rótulo enquanto isSubmitting for true.
Posso usar react-hook-form dentro de um FlatList?
Sim para o wrapper do formulário - coloque campos dentro de ListHeaderComponent ou ListFooterComponent.
Não coloque TextInput não controlado dentro de renderItem sem Controller - a reciclagem resetará os valores.
Para linhas dinâmicas, prefira useFieldArray a estado de índice manual.
Como resetar o formulário após uma chamada de API bem-sucedida?
async function onSubmit(values: RegisterValues) { await createAccount(values); reset(); // volta para defaultValues}
reset() limpa erros, estado sujo e valores.
Passe reset(partialValues) para manter alguns campos após o sucesso (ex: email para um passo de confirmação).
Devo usar FormProvider?
Use-o quando múltiplos componentes filhos precisam de useFormContext() - passos de assistente, grupos de campos em modais, ou seções de formulário de design-system.
Para um formulário de tela única, passar control para TextField é suficiente.
Como react-hook-form se compara ao useState controlado no RN?
useState re-renderiza o componente proprietário a cada onChangeText.
RHF armazena valores em refs e re-renderiza apenas fatias de formState inscritas.
Para 1–2 campos a diferença é insignificante; após ~4 campos RHF vence em hardware de gama média.
Como lidar com toggles de visibilidade de texto seguro e senha?
Mantenha secureTextEntry no TextInput dentro do Controller - não crie um segundo input não controlado.
Alterne a visibilidade com useState local no componente de campo; o valor RHF permanece a mesma string.
Posso combinar react-hook-form com KeyboardAwareScrollView?
Sim - envolva o formulário em KeyboardAwareScrollView de react-native-keyboard-controller.
Defina keyboardShouldPersistTaps="handled" para que os botões de submit funcionem enquanto o teclado está aberto.
Veja Keyboard Controller para bottomOffset e configuração de barra de ferramentas.
Como instalar react-hook-form em um projeto Expo SDK 57?
npx expo install react-hook-form
Adicione @hookform/resolvers e zod quando configurar a validação de esquema.
Nenhum plugin de configuração nativo necessário - JavaScript puro.