【React入門・第4弾】useStateで画面を動かそう!リアルタイム更新フォームの作り方

javascript-react-4 プログラミング

はじめに

以前紹介した第3弾では、「Props」と「配列のmap処理」を使って、データを外から渡して画面を組み立てる方法を学びました。

しかし、これまでの画面は一度表示されたらそのままの「静的」なものでした。Webアプリの醍醐味は、ユーザーがボタンを押したり文字を入力したりすると、それに反応して画面が切り替わることですよね。

そこで第4弾の今回は、Reactの心臓部とも言える「useState(状態管理)」「フォーム入力」を解説します! テキストボックスに入力した文字が、リアルタイムで画面に反映される「リアルタイムプレビュー機能」を体感していきます。

1. そもそも「State」って何?

State(ステート)を直訳すると「状態」です。 ReactにおけるStateとは、「画面に表示するためのデータを覚えておく、特別な変数」のことです。

Pythonや通常のJavaScriptの変数(let name = “名前”)との最大の違いは、「値が更新されたとき、Reactが自動的に画面を再描画(レンダリング)してくれる」という点です。

この機能を使うために、Reactに用意されているのが useState という仕組み(フック)です。

2. リアルタイム・プロフィール編集画面を作ろう

さっそく、名前などを入力すると、リアルタイムプレビューが表示され、プロフィールカードを追加できるコンポーネントを作ってみましょう。

前回までの記事のコードをさらにuseStateを使って進化させます。

デザインには Tailwind CSS を使用しています。

import { useState } from 'react'
import Header from './Header'
import ProfileCard from './ProfileCard'
import CounterButton from './CounterButton'
import './App.css'

function App() {
  // 1. 配列データを useState で管理
  const [members, setMembers] = useState([
    {
      id: 1,
      avatar: '🚀',
      name: 'React a',
      bio: 'フロントエンド開発を勉強中のエンジニアです!',
    },
    {
      id: 2,
      avatar: '🎨',
      name: 'デザイン b',
      bio: 'UI/UXデザインが得意です。最近CSSアニメーションにハマっています。',
    },
    {
      id: 3,
      avatar: '⚽',
      name: 'スポーツ c',
      bio: '休日はサッカーと筋トレ!Web開発でスポーツアプリを作りたいです。',
    },
  ])

  // 2. フォームの入力値を保持する State(プレビュー用にもそのまま使われます!)
  const [avatar, setAvatar] = useState('')
  const [name, setName] = useState('')
  const [bio, setBio] = useState('')

  // 3. カードを追加する処理
  const handleAddMember = (e) => {
    e.preventDefault()

    if (!name.trim()) return

    const newMember = {
      id: Date.now(),
      avatar: avatar || '👤',
      name: name,
      bio: bio,
    }

    setMembers([...members, newMember])

    // 送信後に入力欄とプレビューをリセット
    setAvatar('')
    setName('')
    setBio('')
  }

  return (
    <div className="app-container">
      <Header />

      <main className="main-content">
        {/* カード追加フォーム */}
        <form className="add-member-form" onSubmit={handleAddMember}>
          <h3>新しいメンバーを追加</h3>
          
          <div className="form-group">
            <input
              type="text"
              placeholder="アイコン (例: 🏀)"
              value={avatar}
              onChange={(e) => setAvatar(e.target.value)}
            />
            <input
              type="text"
              placeholder="名前"
              value={name}
              onChange={(e) => setName(e.target.value)}
            />
            <input
              type="text"
              placeholder="自己紹介"
              value={bio}
              onChange={(e) => setBio(e.target.value)}
            />
          </div>

          {/* 「リアルタイムプレビュー」領域 */}
          <div className="preview-container">
            <p className="preview-title"> 完成イメージ(プレビュー)</p>
            {/* ボタンを押す前でも、State (avatar, name, bio) の変化が即座に反映される */}
            <ProfileCard
              avatar={avatar || '❓'}
              name={name || '(名前を入力中...)'}
              bio={bio || '(自己紹介を入力中...)'}
            />
          </div>

          <button type="submit" className="add-button">
            この内容でカードを追加する
          </button>
        </form>

        {/* 確定したメンバー一覧 */}
        <div className="card-list">
          <h3>メンバー一覧</h3>
          {members.map((member) => (
            <ProfileCard
              key={member.id}
              avatar={member.avatar}
              name={member.name}
              bio={member.bio}
            />
          ))}
        </div>

        <CounterButton />
      </main>
    </div>
  )
}

export default App

3. コードの解説

useState の書き方

// 2. フォームの入力値を保持する State

const [avatar, setAvatar] = useState('')
const [name, setName] = useState('')
const [bio, setBio] = useState('')

この部分は、Reactでフォーム(テキスト入力欄)を扱う際の最重要ポイントです!

なぜこのように useState を3つ並べて書いているのか、初心者向けに3つのステップで徹底解説します。

一言でいうと「キーボード入力のメモ帳」

この3行は、ユーザーがキーボードで文字を打ち込んだときに、「今、それぞれの入力欄に何の文字が入っているか」を一時的に記憶しておくためのメモ帳(State) を3個用意しています。

  • avatar ➔ アイコン用入力欄のメモ帳(初期値:'' 空文字)
  • name ➔ 名前用入力欄のメモ帳(初期値:'' 空文字)
  • bio ➔ 自己紹介用入力欄のメモ帳(初期値:'' 空文字)

入力欄(<input>)とStateの仕組み

入力欄のHTML部分を見てみましょう。

<input
  type="text"
  placeholder="名前"
  value={name}                            // メモ帳(State)の中身を表示する
  onChange={(e) => setName(e.target.value)} // キーボードが叩かれたらメモ帳を更新する
/>

ここで行われている「二人三脚」の動き(専門用語で双方向バインディング)は以下の通りです。

【ユーザーの操作】
 ユーザーが「あ」と入力する
       │
       ▼
【onChange が発動】
 `e.target.value`(=「あ」)を拾って `setName('あ')` を実行!
       │
       ▼
【State が更新される】
 `name` の中身が `''` から `'あ'` に変わる
       │
       ▼
【React が画面を再描画】
 `value={name}` によって、入力欄に「あ」が表示される!

一瞬でこのループが回るため、「キーボードを打つ ➔ 即座にStateに保存される」 という状態が作られます。

もし useState で管理しなかったらどうなる?

ユーザー目線ではただの <input> タグでも文字は打ち込めます。

では、「なぜわざわざ onChange で毎回 State を書き換えるのか?」「ボタンを押した時に <input> タグから文字を読み取る方法(従来のHTML/JS)と何が違うのか?」

その違いとReactの仕組みを、3つの理由でスッキリ解説します!

違い1:入力欄を「空(リセット)」にできるかどうか

ボタンを押したあと、入力欄の文字を「消して空にする」動きを想像してみてください。

  • 従来の書き方(inputを後から読む方法)

ボタンが押された後、JavaScriptでわざわざ document.getElementById(‘name-input’).value = ” のように、HTMLの要素を直接操作して消すコードを書く必要があります。

  • Reactの書き方(value={name} + onChange)

State(name)を入力欄とガッチリ紐づけているため、ボタンを押した後の処理で setName(”)(Stateを空にする)を実行するだけ で、入力欄の文字が勝手に消えてリセットされます!

Reactの基本理念: 「画面(HTML)を直接触って操作する」のではなく、「データ(State)を変えれば、画面が勝手に追従する」 という設計になっているためです。

違い2:「ボタンを押す前」に入力チェックや制御ができる

onChange で常に最新の文字を React が把握(監視)していると、以下のような高度な機能がボタンを押す前(入力中)に簡単に作れるようになります。

  • リアルタイム入力チェック: 「名前は10文字以内で入力してください」という警告を入力中にすぐ出せる。
  • ボタンの有効/無効切り替え: 「名前が空のときは、追加ボタンを押せない(グレーアウト)ようにする」(例:<button disabled={!name}>)。
  • リアルタイムプレビュー: 第4弾の冒頭で触れたように、入力した文字がカードを追加する前にプレビュー画面に即座に反映される。

もし「ボタンを押した時にだけ input を読む」方法だと、入力中のリアルタイムな変化に対応できません。

違い3:画面全体の整合性をReactが完璧に管理できる

通常のJavaScriptで「ボタンが押されたら <input> の値を読んで、DOM(HTML)を生成して appendChild で追加する…」という処理を書くと、コードが複雑になり、バグ(画面の表示崩れやデータのズレ)が起きやすくなります。

Reactでは、以下のシンプルな1方向のデータルールだけで動いています。

【ユーザーの操作】
 キーボードを叩く / ボタンを押す
       │
       ▼
【State(データ)の更新】
 `setName(...)` や `setMembers(...)` でデータを変える
       │
       ▼
【Reactの自動描画】
 データに合わせて、画面全体を自動で正しく描き直す!

入力欄の文字(name)も、カードのリスト(members)も、すべてデータ(State)としてReactの管理下に置いておくことで、アプリ全体の動きが予測可能になり、安全に動くようになります。

リアルタイムプレビューの仕組み

次に「プレビュー画面(リアルタイム反映)」とは具体的にどこに作れて、どう動くのかを詳しく解説します。

以下のコードの部分です。

return (
  <div className="app-container">
    <Header />

    <main className="main-content">
      {/* 1. 入力フォーム */}
      <form onSubmit={handleAddMember}>
        <input value={avatar} onChange={(e) => setAvatar(e.target.value)} />
        <input value={name} onChange={(e) => setName(e.target.value)} />
        <input value={bio} onChange={(e) => setBio(e.target.value)} />

        {/* 2. 👁️ リアルタイムプレビュー画面!! */}
        {/* ボタンを押す前でも、State (nameやbio) の文字がそのまま映し出される */}
        <div className="preview-area">
          <p>👇 完成イメージ(プレビュー)</p>
          <ProfileCard
            avatar={avatar || '❓'} // 空なら「❓」を表示
            name={name || '(名前を入力中...)'} 
            bio={bio || '(自己紹介を入力中...)'}
          />
        </div>

        {/* 3. 追加ボタン */}
        <button type="submit">この内容でカードを追加する</button>
      </form>

      {/* 4. ボタンを押して確定したカード一覧 */}
      <div className="card-list">
        {members.map((member) => (
          <ProfileCard key={member.id} {...member} />
        ))}
      </div>
    </main>
  </div>
)

​1. プレビュー画面ってどこに作れるの?

​フォーム(入力欄)とカード一覧(ボタンを押して増える場所)の**「間」**に、入力中の文字がそのまま映し出される「お試しカード」を1枚置くイメージです。

2. なぜ「ボタンを押す前」に動くの?

​ここが React(useState)の最大の面白さです!​

  • キーボードで文字を打つ:onChange が発動して、State(name や bio)の中身が更新される。​
  • Reactが即座に画面を描き直す:<ProfileCard bio=”{bio}” name=”{name}”/> の部分に最新のStateの値がそのまま流れ込むため、キーボードを叩くたびにプレビュー画面のカードがパッパッとリアルタイムで書き換わります!
  • 「追加ボタン」を押す:入力欄とプレビューで確認して「これでOK!」となったタイミングでボタンを押すと、<form onSubmit={handleAddMember}> の handleAddMember 関数が実行されて、その内容が members 配列(確定リスト)に追加されます。

4.まとめ

今回の仕組みでは、 2パターンのState が組み合わさっています。​

  • 入力中の State(avatar, name, bio):​ボタンを押さなくても、キーボードを叩くだけで一瞬で変わる。​「プレビュー表示」 や 「入力欄の文字」 を動かすために使う。​
  • 確定後の State(members 配列):​「追加ボタン」を押した瞬間だけ 変わる。​「下に並ぶカードのリスト」 を増やすために使う。​

「入力中の文字」まで State で管理しているからこそ、このような**「ボタンを押す前に完成形を目で確認できる(プレビュー)」**という機能を使うことができます。

昔のJavaScript(DOM操作)だと、要素を取得してテキストを書き換えて…と面倒な処理が必要でしたが、Reactなら「Stateを更新するだけ」で画面が勝手についてきてくれます。

次回・第5弾では、今回の知識をさらに発展させて、「配列のState管理(ToDoリストのようにデータを追加・削除する動き)」に挑戦します。

コメント