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

第4回React入門のuseStateによる更新フォームの概要 プログラミング

はじめに

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

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

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

​事前準備:本記事では、第2回で作った Header, ProfileCard, CounterButton コンポーネントと、App.css を使用します。まだ作成していない方は、先に第2,3回の記事をご覧ください。

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

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

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

Reactのstateの状態変化の流れ
  • ​普通の変数: 値を変えても、画面上の表示は変わらない(手動で書き換える必要がある)。​
  • State: 値を変えると、画面の表示も勝手に変わる。

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

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

さっそく、名前などを入力するとリアルタイムプレビューが表示され、「追加」ボタンでメンバーリストを増やせる画面を作ってみましょう。前回のコードをさらに進化させます。​

src/App.jsx を以下のコードに書き換えてください。

CSSクラスは第2回のものをベースにしています。

import { useState } from 'react'
import Header from './components/Header'
import ProfileCard from './components/ProfileCard'
import CounterButton from './components/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アニメーションにハマっています。',
    },
  ])
  // 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(), // 簡易的な一意のID
      avatar: avatar || '👤', // 空ならデフォルトアイコン
      name: name,
      bio: bio,
    }
    // 【State更新】元の配列(...members)の最後に新しいメンバーを追加した新しい配列をセット
    setMembers([...members, newMember])
    // 送信後、入力欄とプレビューのStateを空にする(リセット)
    setAvatar('')
    setName('')
    setBio('')
  }
  return (
    <div className="app-container">
      <Header />
      <main className="main-content">
        
        {/* --- カード追加フォーム領域 --- */}
        {/* onSubmit で、エンターキーやボタン押下時に handleAddMember を実行 */}
        <form className="profile-card" onSubmit={handleAddMember} style={{padding: '2rem', marginBottom: '2rem', border: '2px solid #ddd'}}>
          <h3 style={{marginBottom: '1rem'}}>新しいメンバーを追加</h3>
          
          {/* 各 input の value に State を紐づけ、onChange で State を更新 */}
          <div style={{display: 'flex', flexDirection: 'column', gap: '0.5rem', marginBottom: '1.5rem'}}>
            <input
              type="text"
              placeholder="アイコン (例: 🏀)"
              value={avatar}
              onChange={(e) => setAvatar(e.target.value)}
              style={{padding: '0.5rem', borderRadius: '4px', border: '1px solid #ccc'}}
            />
            <input
              type="text"
              placeholder="名前(必須)"
              value={name}
              onChange={(e) => setName(e.target.value)}
              style={{padding: '0.5rem', borderRadius: '4px', border: '1px solid #ccc'}}
            />
            <input
              type="text"
              placeholder="自己紹介"
              value={bio}
              onChange={(e) => setBio(e.target.value)}
              style={{padding: '0.5rem', borderRadius: '4px', border: '1px solid #ccc'}}
            />
          </div>
          {/* 👁️ 「リアルタイムプレビュー」領域 */}
          <div style={{marginBottom: '1.5rem', textAlign: 'center'}}>
            <p style={{fontSize: '0.9rem', color: '#666', marginBottom: '0.5rem'}}>👇 完成イメージ(プレビュー)</p>
            {/* まだ確定前(members配列に入れる前)の State (avatar, name, bio) をそのまま Props で渡す */}
            <ProfileCard
              avatar={avatar || '❓'} // 空なら「❓」を表示
              name={name || '(名前を入力中...)'} 
              bio={bio || '(自己紹介を入力中...)'}
            />
          </div>
          {/* submitボタン */}
          <button type="submit" className="like-button" style={{width: '100%'}}>
            この内容でカードを追加する
          </button>
        </form>
        {/* ------------------------------- */}
        {/* 確定したメンバー一覧(前回の繰り返し描画) */}
        <div className="main-content" style={{width: '100%'}}>
          <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

(※コード内の style={{…}} は、第2回のCSSに加えて一時的に適用しているスタイルです。コピペで動作します。)

ブラウザを確認してください。入力欄に文字を打つとプレビューが変わり、ボタンを押すと下にカードが増えるはずです!

3. コードの解説(双方向バインディングの仕組み)

Reactでフォーム(テキスト入力欄)を扱う際、最も大切なポイントを解説します。

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に保存される」 という状態が作られます。

Reactのstateの同期サイクル

​📝 自分の経験談:昔のJavaScript(DOM操作)で挫折しかけた話
​Reactを学ぶ前、私はjQueryなどの「DOM操作」でフォーム入力を作っていました。「入力欄の値を取得して、IDを指定して、要素を作成して…」動きが複雑になるにつれてコードは長くなり、バグが出てもどこが悪いのかわかりずらい。​しかしReactに出会い、「データ(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を更新するだけ」で画面が勝手についてきてくれます。

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

是非以下の第5回の記事をご覧ください。

コメント