Adam WolffAdam Wolff
BlogProjectsAbout
© Adam Wolff 2025
TwitterGithub

React Native Forms

Form management in React and React Native has traditionally been challenging. While libraries have attempted to ease this pain, many have inadvertently made things worse. The introduction of hooks significantly improved the situation, enabling developers to leverage powerful libraries like react-hook-form to manage form state more effectively.

This article explores creating an ideal form experience by examining unified form state management and component architecture.

Basic Example

A naive form implementation using individual useState hooks becomes unwieldy as complexity grows:

import React, { useState } from 'react'
import { View, TextInput, Button, StyleSheet, Alert } from 'react-native'

function BasicForm() {
  const [text, setText] = useState(null)
  const [age, setAge] = useState(null)

  const submitHandler = () => {
    Alert.alert('Form Submitted!', `text: ${text} & age: ${age}`, [
      { text: 'OK' }
    ])
  }

  return (
    <View>
      <TextInput
        style={styles.input}
        onChangeText={setText}
        placeholder="name"
        value={text}
      />
      <TextInput
        style={styles.input}
        onChangeText={setAge}
        value={age}
        placeholder="age"
        keyboardType="numeric"
      />
      <Button title="submit" onPress={submitHandler} />
    </View>
  )
}

const styles = StyleSheet.create({
  input: {
    paddingHorizontal: 10,
    height: 40,
    margin: 12,
    borderWidth: 1
  }
})

export default BasicForm

Problems with This Approach

  1. Scalability Issue: Multiple useState calls become cumbersome and non-declarative as inputs increase
  2. No Validation: Users can submit empty forms or invalid data without constraints
  3. Poor Keyboard UX: No seamless transitions between inputs via the keyboard

React-Hook-Form Solution

React-hook-form delivers "performant, flexible and extensible forms with easy-to-use validation" leveraging React hooks to register inputs into unified form state.

Installation

npm install react-hook-form

Core Hooks

  • useForm: Manages overall form state and provides control mechanisms
  • useController: Registers individual inputs with the form state

Basic Implementation

First, extract control and handleSubmit from useForm:

import { useForm, useController } from 'react-hook-form'

function HookForm() {
  const { control, handleSubmit } = useForm()
}

Create a reusable Input wrapper component:

function Input({ name, control, ...rest }) {
  const { field } = useController({
    control,
    name
  })

  return (
    <TextInput
      {...rest}
      style={styles.input}
      value={field.value}
      onChangeText={field.onChange}
      placeholder={name}
    />
  )
}

Implement the form:

function HookForm() {
  const { control, handleSubmit } = useForm()

  const onSubmit = data => {
    // Returns: { "name": "value", "age": "value" }
    console.log(data)
  }

  return (
    <View style={styles.container}>
      <Input name="name" control={control} />
      <Input name="age" control={control} />
      <Button title="submit" onPress={handleSubmit(onSubmit)} />
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    backgroundColor: 'whitesmoke'
  },
  input: {
    paddingHorizontal: 10,
    height: 40,
    margin: 12,
    borderWidth: 1
  }
})

export default HookForm

Validation

Extend the Input component to accept and apply validation rules:

function Input({ name, control, rules, ...rest }) {
  const { field, fieldState } = useController({
    control,
    name,
    rules
  })

  return (
    <TextInput
      {...rest}
      style={[styles.input, fieldState.error && { borderColor: 'red' }]}
      value={field.value}
      onChangeText={field.onChange}
      placeholder={name}
    />
  )
}

Apply validation rules to inputs:

<Input
  name="name"
  control={control}
  rules={{ required: true }}
/>

<Input
  keyboardType="numeric"
  name="age"
  control={control}
  rules={{ required: true, validate: value => value >= 18 }}
/>

The validate property executes custom logic, preventing form submission if validation fails and applying error styling to affected inputs.

Input Transitions

React Native lacks built-in sibling awareness for inputs, requiring manual ref management for smooth keyboard navigation:

function Transitions() {
  const ageInput = useRef()
  const buttonInput = useRef()
  const { control, handleSubmit } = useForm()

  const onSubmit = data => console.log(data)

  return (
    <View>
      <Input
        name="name"
        control={control}
        onSubmitEditing={() => ageInput.current.focus()}
      />
      <Input
        ref={ageInput}
        name="age"
        control={control}
        onSubmitEditing={() => buttonInput.current.props.onPress()}
      />
      <Button
        ref={buttonInput}
        title="submit"
        onPress={handleSubmit(onSubmit)}
      />
    </View>
  )
}

Using onSubmitEditing callbacks on TextInput components enables users to navigate between fields and trigger submission by pressing the return key, creating a polished mobile form experience.

Resources

Example repository: github.com/Staceadam/blog-examples