React Native Course
Forms

TextInput と useState で入力を制御し、「ラベル → 入力欄 → state」の読み方を練習する第4話です。

フォーム入力とバリデーション - 入力値を state で追う

第2話 では、ボタンを押すたびに変わる数字useState で追いました。今回は キーボード入力 が state に入るフォームを扱います。

登録画面で名前を打ち込むとき、「この入力欄の値はコードのどこで持っているのか」と感じたことはないでしょうか。読み方の軸は第2話と同じです。画面上の値 → {変数}useState。第4話では TextInput を使った制御コンポーネントと、簡単な必須チェックまでを一通り動かします。

この記事で扱うこと

実装(A)読み解き(B)
制御コンポーネントTextInput + value / onChangeTextラベル文言 → Input → state 変数
送信Pressable + 簡単な必須チェックエラーメッセージ → バリデーション条件
第2・3話との接続同じ state の読み方Expo Router 画面内でも同型
まとめ次に進む順番第5話への導線

前提: 第1話第2話。第3話の Expo Router は任意です(app/signup.tsx のような画面ファイルでも読み方は同じです)。

1. TextInput の最小例 — 制御コンポーネント

第2話のカウンターと同様、ここでは 1 画面・1 ファイル に絞ります。エントリファイル(App.tsxapp/index.tsx など)を次の内容に差し替えてください。

import { useState } from "react";
import {
  Pressable,
  StyleSheet,
  Text,
  TextInput,
  View,
} from "react-native";

export default function SignUpScreen() {
  const [name, setName] = useState("");

  return (
    <View style={styles.container}>
      <Text style={styles.label}>お名前</Text>
      <TextInput
        style={styles.input}
        value={name}
        onChangeText={setName}
        placeholder="山田 太郎"
      />
      <Text style={styles.preview}>入力中: {name}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 24,
    justifyContent: "center",
    gap: 12,
  },
  label: {
    fontSize: 14,
    fontWeight: "600",
  },
  input: {
    borderWidth: 1,
    borderColor: "#cbd5e1",
    borderRadius: 8,
    padding: 12,
    fontSize: 16,
  },
  preview: {
    color: "#64748b",
  },
});

bunx expo start で起動し、入力欄に文字を打つと「入力中: …」がリアルタイムで変われば成功です。

ポイントは次の 3 つです。

  • TextInputvalueonChangeText をセットで 渡す(制御コンポーネント)
  • placeholder は未入力時のヒント。実際の値は value={name} で持つ
  • 第2話の count / setCount と同型で、name / setName が入力用の state になる

2. 読み解きの練習 — ラベルから state まで

第1・2話と同じ型で、いま画面に見えているものから入ります。

  1. 画面上のラベル「お名前」をエディタで全文検索する
  2. その直下の TextInput を確認する
  3. value={name}onChangeText={setName} を辿り、第2話の count / setCount と対比する
  4. 「入力中: {name}」のプレビューで、キー入力 → state 更新 → 再描画 を言語化する

流れを言葉にすると、こうなります。

キー入力 → onChangeText(setName)→ name が更新 → 再描画 → 入力欄とプレビューが変わる

読み解きのキーワードは次のとおりです。

ラベル文言 → TextInput → value / onChangeText → useState

3. 送信と簡単なバリデーション

送信ボタンとエラー表示を足します。空のまま送ろうとしたときだけ、メッセージを出す最小例です。

import { useState } from "react";
import {
  Pressable,
  StyleSheet,
  Text,
  TextInput,
  View,
} from "react-native";

export default function SignUpScreen() {
  const [name, setName] = useState("");
  const [error, setError] = useState("");

  function handleSubmit() {
    if (name.trim() === "") {
      setError("お名前を入力してください");
      return;
    }
    setError("");
    // 第5話で API 送信へ拡張。ここではログで十分
    console.log("送信:", name.trim());
  }

  return (
    <View style={styles.container}>
      <Text style={styles.label}>お名前</Text>
      <TextInput
        style={styles.input}
        value={name}
        onChangeText={setName}
        placeholder="山田 太郎"
      />
      {error ? <Text style={styles.error}>{error}</Text> : null}
      <Pressable style={styles.button} onPress={handleSubmit}>
        <Text style={styles.buttonLabel}>送信</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 24,
    justifyContent: "center",
    gap: 12,
  },
  label: {
    fontSize: 14,
    fontWeight: "600",
  },
  input: {
    borderWidth: 1,
    borderColor: "#cbd5e1",
    borderRadius: 8,
    padding: 12,
    fontSize: 16,
  },
  error: {
    color: "#dc2626",
    fontSize: 14,
  },
  button: {
    marginTop: 8,
    paddingVertical: 12,
    borderRadius: 8,
    backgroundColor: "#0f172a",
    alignItems: "center",
  },
  buttonLabel: {
    color: "#fff",
    fontSize: 16,
    fontWeight: "600",
  },
});

名前を入れずに「送信」を押すと赤いエラーが出ます。名前を入れて再度押すとエラーが消え、コンソールに 送信: … が出ます。

読み解きの練習 — エラー文言から条件まで

  1. 画面の「お名前を入力してください」をエディタで検索する
  2. {error} または setError("お名前を入力してください") を特定する
  3. handleSubmit 内の if (name.trim() === "") まで辿る

エラー表示も、第2話の数字と同じく state(error)→ JSX の流れで読めます。

つまずきやすい点

症状原因の目安
入力しても文字が消える / 打てないvalue だけあって onChangeText がない
エラーが消えない成功時に setError("") しているか
送信しても何も起きないonPress={handleSubmit} が付いているか

日本語入力(IME)の途中変換や、キーボードで入力欄が隠れる問題は本記事では扱いません。KeyboardAvoidingView などは必要になったときに調べれば十分です。

4. 第2話・第3話との接続

接続
第2話count / setCountname / setName。更新の仕組みは同じ
第3話app/signup.tsx のような画面ファイルでも、ファイル内の読み方は同型

メールアドレスなどフィールドが増えたら、state を分けます。

const [name, setName] = useState("");
const [email, setEmail] = useState("");

本記事では 名前 1 つに絞る と、ラベル → Input → state の練習がしやすいです。react-hook-form や Zod などのライブラリはスコープ外です。サーバーへの POST 送信も、第5話で fetch の入口を扱います。

5. まとめ

実装の順番

  1. TextInputvalueonChangeText を渡す
  2. 入力値は useState で持つ
  3. 送信前に簡単な必須チェックを入れる

読み解きの順番

  1. ラベルまたはプレースホルダーから TextInput を探す
  2. value={...} から state 変数を特定する
  3. エラー文言からバリデーション条件を辿る

ここまでできれば、ユーザー入力を扱う画面を読める土台ができています。