フォームとキーボード
モバイルのフォームはキーボード制御が大半の難易度を占める。 入力欄が隠れない、Tab/Next で次に飛べる、安全に閉じられる、ところまで設計できれば一人前。
<TextInput> の主要 props
| prop | 意味 |
|---|---|
value / onChangeText | 制御コンポーネント。onChange はイベントオブジェクトを返すので普通は onChangeText |
placeholder | プレースホルダ |
placeholderTextColor | プレースホルダの色 |
keyboardType | "default" / "email-address" / "numeric" / "phone-pad" / "decimal-pad" / "url" |
autoCapitalize | "none" / "sentences" / "words" / "characters" |
autoCorrect | 自動修正(メール等で off に) |
autoComplete | "email" / "password" / "name" 等。OS の入力候補が出る |
textContentType (iOS) | iOS 専用ヒント。"emailAddress" / "oneTimeCode" など |
secureTextEntry | パスワード(マスク) |
multiline | 複数行 |
numberOfLines | multiline 時の表示行数 |
maxLength | 最大文字数 |
returnKeyType | "done" / "next" / "send" / "go" |
onSubmitEditing | キーボードの完了押下 |
blurOnSubmit | 送信時にキーボード閉じる(multiline は既定で false) |
selectTextOnFocus | フォーカス時に全選択 |
editable | 編集可否 |
制御コンポーネント基本
import { TextInput } from "react-native"
import { useState } from "react"
function EmailInput() {
const [email, setEmail] = useState("")
return (
<TextInput
value={email}
onChangeText={setEmail}
placeholder="メールアドレス"
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
autoComplete="email"
textContentType="emailAddress"
style={{ borderWidth: 1, padding: 10, borderRadius: 8 }}
/>
)
}
キーボードを閉じる
外をタップで閉じる
import { TouchableWithoutFeedback, Keyboard, View } from "react-native"
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={{ flex: 1 }}>
{/* 中身 */}
</View>
</TouchableWithoutFeedback>
プログラム的に閉じる
import { Keyboard } from "react-native"
Keyboard.dismiss()
キーボードで隠れないようにする
<KeyboardAvoidingView>(標準)
フォームを囲うとキーボード分だけ画面が押し上げられる。iOS は "padding"、Android は "height" または undefinedがよく使われる。
import { KeyboardAvoidingView, Platform } from "react-native"
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === "ios" ? "padding" : undefined}
keyboardVerticalOffset={Platform.OS === "ios" ? 64 : 0}
>
<ScrollView>
<TextInput ... />
</ScrollView>
</KeyboardAvoidingView>
react-native-keyboard-controller(推奨)
標準の KeyboardAvoidingView は挙動がプラットフォームで揺れる。
2026 現在は react-native-keyboard-controller がより滑らかで定番化している。
npx expo install react-native-keyboard-controller
import { KeyboardAvoidingView, KeyboardProvider } from "react-native-keyboard-controller"
// アプリのルートで包む
<KeyboardProvider>
<Slot />
</KeyboardProvider>
// 各画面で
<KeyboardAvoidingView behavior="padding" style={{ flex: 1 }}>
...
</KeyboardAvoidingView>
Next/Done で次に飛ぶ
returnKeyType + onSubmitEditing でキーボードの「次へ」ボタンを活用。
import { useRef } from "react"
const passwordRef = useRef<TextInput>(null)
<TextInput
placeholder="メール"
returnKeyType="next"
onSubmitEditing={() => passwordRef.current?.focus()}
blurOnSubmit={false}
/>
<TextInput
ref={passwordRef}
placeholder="パスワード"
secureTextEntry
returnKeyType="done"
onSubmitEditing={submit}
/>
パスワードと OTP
パスワード入力 + 表示切替
const [show, setShow] = useState(false)
<View style={{ flexDirection: "row" }}>
<TextInput
secureTextEntry={!show}
autoComplete="password"
textContentType="password"
style={{ flex: 1 }}
/>
<Pressable onPress={() => setShow(s => !s)}>
<Text>{show ? "🙈" : "👁"}</Text>
</Pressable>
</View>
SMS の OTP 自動入力(iOS)
<TextInput
textContentType="oneTimeCode"
autoComplete="sms-otp"
keyboardType="number-pad"
/>
iOS は SMS が来たらキーボード上に「コードを入力」が出てワンタップで入る。Android は autoComplete="sms-otp" で自動入力。
バリデーション
シンプル: useState で手書き
const [email, setEmail] = useState("")
const [error, setError] = useState<string | null>(null)
const validate = () => {
if (!email.includes("@")) { setError("メールが不正"); return false }
setError(null); return true
}
<TextInput value={email} onChangeText={setEmail} />
{error && <Text style={{ color: "red" }}>{error}</Text>}
本格的: react-hook-form + zod
Web と同じく RN で使える定番の組み合わせ。
npm install react-hook-form zod @hookform/resolvers
import { useForm, Controller } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { z } from "zod"
const Schema = z.object({
email: z.string().email("メールが不正"),
password: z.string().min(8, "8文字以上"),
})
type Form = z.infer<typeof Schema>
export default function Login() {
const { control, handleSubmit, formState: { errors } } = useForm<Form>({
resolver: zodResolver(Schema),
})
const onSubmit = (data: Form) => signIn(data)
return (
<>
<Controller
control={control}
name="email"
render={({ field: { value, onChange, onBlur } }) => (
<TextInput
value={value} onChangeText={onChange} onBlur={onBlur}
placeholder="メール"
/>
)}
/>
{errors.email && <Text style={{ color: "red" }}>{errors.email.message}</Text>}
<Controller
control={control}
name="password"
render={({ field: { value, onChange, onBlur } }) => (
<TextInput
value={value} onChangeText={onChange} onBlur={onBlur}
secureTextEntry placeholder="パスワード"
/>
)}
/>
{errors.password && <Text style={{ color: "red" }}>{errors.password.message}</Text>}
<Pressable onPress={handleSubmit(onSubmit)}><Text>送信</Text></Pressable>
</>
)
}
選択系
Picker
標準の <Picker> は廃止。@react-native-picker/picker を使う。
npx expo install @react-native-picker/picker
日付・時刻
npx expo install @react-native-community/datetimepicker
import DateTimePicker from "@react-native-community/datetimepicker"
<DateTimePicker
value={date}
mode="date" // "date" | "time" | "datetime"
display="default"
onChange={(event, selectedDate) => setDate(selectedDate ?? date)}
/>
キーボードのエッジケース
iPad のスプリットキーボード
useKeyboard や Keyboard Controller の API でキーボード高さを取得すると安全。
外部キーボード接続時
ハードウェアキーボードがあると onSubmitEditing がうまく動かないことがある。
onKeyPress で "Enter" を見るのが代替策。
Android の windowSoftInputMode
app.json の android.softwareKeyboardLayoutMode で "pan"(既定)/ "resize" を選べる。
スクロールビューで挙動がおかしい時はここを切り替えると治ることがある。
アクセシビリティ
- すべての TextInput に
accessibilityLabel。視覚的なラベルが別にあるなら、その文字列と一致させる。 - エラーは
accessibilityLiveRegion="assertive"(Android)+role="alert"相当のフォーカス管理。 - 必須項目はテキストで「必須」と明記。色だけでなくテキストでも表示する。
送信ボタンのトグル
通信中は2重送信を防ぐのと、ローディングを表示するのが基本。
<Pressable disabled={mutation.isPending} onPress={handleSubmit(onSubmit)}>
{mutation.isPending ? <ActivityIndicator /> : <Text>送信</Text>}
</Pressable>
✓ 適切な keyboardType / autoCapitalize / autoComplete
✓ Next で次のフィールドへ飛べる
✓ 外タップで閉じる、または KeyboardAvoidingView で隠れない
✓ エラーメッセージが視覚 + アクセシビリティで伝わる
✓ 送信中は2重送信を防ぐ