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.
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
useState calls become cumbersome and non-declarative as inputs increaseReact-hook-form delivers "performant, flexible and extensible forms with easy-to-use validation" leveraging React hooks to register inputs into unified form state.
npm install react-hook-form
useForm: Manages overall form state and provides control mechanismsuseController: Registers individual inputs with the form stateFirst, 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
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.
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.
Example repository: github.com/Staceadam/blog-examples