初級17分で読める

検索フォームで学ぶReact:入力のstate・確定した条件・子へのprops

検索ボタンで確定する仕様を、Reactのstateとpropsでどう実装するか。入力ごとに絞る方式への変更方針と、実際の教材検索の設計・検証記録を読めます。

更新: 2026-09-13

制作・運営:React Guide 運営者

Reactの実装を読む

検索の仕様を、stateとpropsに落とし込む

ここでは入力途中のdraftと、検索時に確定するsubmittedを別々のstateに持ちます。結果はsubmittedから計算して、子へpropsで渡します。ボタンで確定する仕様を、どの値と処理で実現するか確認します。

別の検索画面を作るなら、どちらの仕様を選ぶ?
入力中のstate:draft
(空欄)
確定済みのstate:submitted
(空欄)

子コンポーネントが受け取る結果

  • stateの更新
  • propsで値を渡す
  • Effectと通信

入力中と確定済みの値を上の表示で確認できます。入力のたびに絞る仕様へ変えるなら、resultsの計算元を何に変え、どのstateが不要になるでしょう?

本番検索からURL連携と全教材データを除いた学習用の実装です。入力・予想・説明はこの画面内だけで扱い、保存・送信・自動採点しません。再読み込みすると消えます。

この教材で理解すること

前提は、JavaScriptの関数・配列と、ReactのuseStateの基本です。冒頭の実験は、入力・確定・結果の3つを観察するための小さなReactアプリです。

  • 入力中の値と、検索を確定した値には、別々の役割がある。
  • stateの更新で再レンダーされても、検索結果の内容まで変わるとは限らない。
  • 確定済みの条件から計算できる結果は、別のstateに保存しなくてもよい。
  • 子はpropsで結果を受け取り、親が検索条件を持つ。

検索ボタンを押して結果を変えるのは、利用者には普通の動作です。この教材では、その動作を実装する値の持ち方を読み解きます。入力のたびに絞る仕様へ変更するなら、resultsをdraftから計算し、submittedと確定処理を外せます。2つのstateは常に必要なのではなく、今回選んだ仕様に対応しています。

入力から表示までを追う

実験の親コンポーネントは、入力中の draft と、確定済みの submitted を持ちます。

const [draft, setDraft] = useState('')
const [submitted, setSubmitted] = useState('')
const results = lessons.filter(lesson =>
  lesson.toLowerCase().includes(submitted.toLowerCase())
)
// 入力欄:value={draft}、onChangeでsetDraftを呼ぶ
// フォーム送信:setSubmitted(draft.trim())を呼ぶ
// 結果の表示:<Results items={results} />

これは実験の抜粋です。実験コンポーネントのソース全文には入力欄と子コンポーネントも含まれます。

入力したときはdraftが変わり、親が再レンダーされます。resultsの計算も実行されますが、使っているsubmittedは前のままなので、一致する教材は同じです。filterは新しい配列を作るため「同じ配列オブジェクトだから更新されない」という説明でもありません。

確定ボタンを押すとsubmittedが変わり、次のレンダーでは新しい語で絞ります。Resultsはitemsをpropsとして受け取り、一覧を描画します。Reactが自動的に検索ボタンの意味を理解しているのではなく、どのイベントでどのstateを更新するかを実装で決めています。

結果をもう一つのstateにしたら?

この小さな実験では結果は「教材一覧とsubmitted」から決まります。resultsまでstateで保持すると、検索条件を変えたのに結果側の更新を忘れるなど、同期する箇所が増えます。毎回計算する方が、結果の根拠を一か所にできます。

ただし、サーバーから受け取った結果は、手元の検索語だけから作れるデータではありません。外部の取得結果をstateに持つ場面まで否定しているわけではありません。今回の実験は、固定の3教材をブラウザ内で検索します。

別の画面に使ってみる

解説を閉じて、プロフィール編集を考えてください。入力中の名前は変えたいけれど、保存するまで公開表示は変えたくありません。

  1. 編集中と公開中の名前を、どこで分けますか?
  2. 「キャンセル」を押したら、どの値をどの値へ戻しますか?
  3. 子コンポーネントの公開プロフィールには、どちらの名前をpropsで渡しますか?

コードを思い出せなくても、値の役割と更新するタイミングを説明してみてください。翌日にもう一度、検索の例を見ずに考えれば、解説を読んだ直後とは違う確認になります。回答は自動採点せず、学習効果が確認済みという扱いにもしていません。

実験と本番検索の違い

実験は状態の役割を見せるため、入力中の値もReactのstateで持ちます。本サイトの本番検索は、入力中の値をDOMの入力欄に持ち、確定した値をURLに残します。これは同じコードではありません。

共通するのは「編集中と確定済みを分ける」という設計です。学習用のuseState版を理解した後に、再読込や戻る操作の要件が加わると保存場所がどう変わるかを、以下の制作記録で確認できます。

作ったものと、この記録の立場

このサイトの記事一覧に、教材検索を追加しました。完成した検索を開くと、シリーズ内の教材も含めて探せます。

これは、この機能をAIコーディングエージェントで実装し、自動テストした記録です。運営者が長年の実務で身につけたノウハウを語る記事でも、人による監修済みという記録でもありません。下の仕様と設計判断は、この制作でエージェントが具体化したものです。運営者による個別の承認・実機評価は未実施です。

最初の依頼は「制作と検証の過程を教材にする」という方針への了承でした。詳細な検索仕様まで指示されていたわけではありません。ここで重要なのは、AIに任せた判断を読者から見えなくしないことです。

実装前に、完成条件を言葉にする

「検索欄をつけて」だけでは、次の答えが決まりません。今回は小さな機能として、こう定義しました。

判断すること 今回の仕様 選んだ理由
対象 単発記事とシリーズ内の全教材 記事一覧に出ていないHooks教材も探したい
検索する場所 タイトル・説明・シリーズ名・タグ 本文まで含めると、例として触れただけの語でも一致する
複数の語 空白で分け、すべて含むものを返す 「useEffect 通信」のように絞り込める
英字の表記 大小文字と全角・半角を揃える USEEFFECTでもuseEffectを探せる
空欄 全教材を表示 条件を減らして一覧へ戻れる
0件 0件と明示し、語を減らす案内を出す 故障や読み込み中と区別する
確定のタイミング 検索ボタン、または通常のフォーム送信 入力するたびに結果やURLを変えない
戻る・再読込 URLのqを確定済みの検索語にする 同じ条件を再現できる
タグ付きの入口 タグ内で検索し、その状態を明示 既存の絞り込みを勝手に消さない

これらはReactが自動的に決めてくれることではありません。例えば「空欄では何も表示しない」という別の仕様も成立します。AIが動くものを作っても、欲しかった仕様かどうかは別に確認する必要があります。

既存コードを読むと、対象の落とし穴が見えた

改修前の記事一覧は、通常は単発記事だけを返す getAllArticles() を使っていました。一方、タグの絞り込みではシリーズ記事も返していました。

そのまま通常一覧の配列を絞るだけでは、シリーズ内にある useEffect の基本記事を検索できません。これは今回の変更前にコードを読んで見つけた構造上の違いです。「AIが一度失敗する実装を出した」という出来事を後から作ったものではありません。

そこで getSearchableArticles() を追加し、単発記事とシリーズ記事を集めます。シリーズ記事は seriesSlug を残し、検索結果から正しいURLへ移動できるようにします。

読者が同じ状況に出会ったら、検索ロジックより先に「そもそも候補に入っているか」を調べてください。候補にない記事は、検索条件を直しても出てきません。

検索そのものは、Reactの外に置く

検索対象と文字列から結果を計算する処理は、普通のTypeScript関数にしました。

function normalize(value: string): string {
  return value.normalize('NFKC').toLowerCase().trim()
}

// articlesはタイトル・説明・シリーズ名・タグを持つ教材の配列
const terms = normalize(query).split(/\s+/).filter(Boolean)
const results = articles.filter(article => {
  const text = normalize([
    article.title, article.description,
    article.series ?? '', ...article.tags,
  ].join(' '))
  return terms.every(term => text.includes(term))
})

これは実装の中心部分の抜粋です。型を含む実装全文も確認できます。

every() を使うため、すべての語が見つかった記事だけ残ります。空欄のときは語が0個になり、すべての候補が条件を満たします。正規表現として検索語を実行しないため、.* と入力しても全件一致にはなりません。

NFKCは全角英字以外の互換文字も変換します。原文を保存する処理には使わず、比較用の文字列だけに使っています。ひらがな・カタカナや同義語をすべて同一視する機能ではありません。MDNのnormalizeの説明にある変換の範囲も確認できます。

検索結果を別のstateに保存して、Effectで毎回同期する構成にはしていません。候補と確定した検索語からその場で計算できます。React公式の説明でも、表示用のデータ変換に不要なEffectを加えない考え方が紹介されています。

入力中の語と、検索済みの語を分ける

入力欄は編集中の値を持ち、検索の確定時に FormData で読み出します。確定した値は URLSearchParams の q に保存します。

const next = new URLSearchParams(params)
next.set('q', String(data.get('q') ?? '').trim())
setParams(next)

既存のparamsをコピーするので、タグ条件は残ります。URL文字列へ検索語を直接連結しないため、& などを検索語として渡す際もURLのエンコードを任せられます。

URLを変更するとブラウザの履歴にも条件が残ります。ただし、入力欄に defaultValue を設定するだけでは、履歴で戻ったときに値が追従するとは限りません。今回はフォームの key を確定済みの検索語とタグから作り、条件が変わったときに入力欄を作り直します。

その結果、未送信の編集内容は、別の検索条件へ戻った際に破棄されます。今回は「確定した検索を再現する」を優先しました。下書きを残したいフォームでは別の設計が必要です。

独自のEnterキー処理は追加していません。ただし、それだけで全OSの日本語IMEで正しく動くと保証はできません。今回の自動テストは日本語文字列の入力を確認したもので、実際の変換候補の選択や変換確定を再現したものではありません。

読者が再現できる確認手順

  1. 教材検索で USEEFFECT を入力して検索する。シリーズの基本記事が見つかるか確認する。
  2. 存在しない教材xyz で検索する。0件の説明が出るか確認する。
  3. ブラウザで戻る。前の検索語が入力欄へ戻り、結果も戻るか確認する。
  4. 再読み込みする。同じ検索語と結果になるか確認する。
  5. useEffectタグから 通信 で検索する。URLにタグと検索語の両方が残るか確認する。
  6. タグを解除して空欄で検索する。単発記事だけでなくシリーズの教材も表示されるか確認する。
  7. スマートフォン幅で、検索欄とボタンが画面の外へはみ出さないか確認する。

「検索ボタンが押せた」と「欲しい教材を探せた」は同じではありません。検索結果に出るはずの具体的な記事を一つ決めて確認します。

実施した検証と、残っている確認

2026年9月12日、Node.js 24.6.0、React 19.2.5、React Router 7.14.2、Playwright 1.60.0の環境で実行しました。ブラウザ検証はPlaywrightのChromiumです。

検証 実施結果
検索関数の5テスト 空欄、全角・大小文字、複数語、説明・シリーズ名、正規表現風の入力がすべて成功
検索のブラウザテスト シリーズ記事の発見、0件、履歴で戻る、再読込、タグ併用、空欄、390px幅の確認が成功
サイト全体の単体テスト 13ファイル・155テスト成功
ビルド後のページ検査 73ページのメタ情報・内部リンク・アンカーの検査が成功
既存教材を含むブラウザテスト 4テスト成功

今回の自動テストでは失敗は検出されませんでした。意図的な不具合を混ぜて「AIの失敗→修正」という筋書きにはしていません。仕様を先に決め、実装後に確かめた例です。

実装と照合したい方は、検索画面のソースと検索関数のテストを参照してください。これらはビルド時に実ファイルから出力しています。画面のソースは本サイトのルーター・教材データ・コンポーネントに依存し、単独で動く完成プロジェクトではありません。

本リポジトリで実行したコマンドは次のとおりです。ブラウザテストにはPlaywrightのブラウザが必要です。

npm run lint
npm run test
npm run build
npm run check:quality
npm run test:review

この機能はブラウザ内の教材データを検索します。検索用の外部APIはありません。ただし検索語はURLに入るため、再読み込み時のサーバーログや既存のアクセス解析などにURLが記録される可能性があります。秘密情報を検索語として入力する用途ではありません。

未確認なのは、実機の日本語IME、スクリーンリーダーでの使い勝手、大量の記事での速度、読者が実際に使う検索語の十分な網羅です。自動テストが通ることと、検索体験が良いことは分けて考えます。

Reactに絞って学ぶ意味と、その限界

今回の文字列処理はReact専用ではありません。Reactを使っているのは、入力欄・URL・結果の表示を既存サイトへ組み込む部分です。

このサイトのReact環境で一つの機能を作ることで、要求から表示、検証までを同じ題材で追えます。検索対象を決める、期待する記事を決める、未確認を残すという手順は他のフレームワークでも使えます。ReactだからAIの出力が正しくなる、という結論にはなりません。

次に取り組むなら、実際に探したかった教材と検索語を数件集め、見つからなかった理由を分類します。表記の違いなのか、説明に語がないのか、そもそも教材がないのか。機能を追加する前に、その違いを見つけることが次の判断材料になります。

#react#search#testing#build-log