こんにちは!Reactの学習、楽しく進められていますか?
画面を自分好みに動かせるようになってくると本当にワクワクしますよね。でも、その一方で「あれ?なんだか思っていた通りに画面が動かないぞ…?」と、頭を抱えてしまう瞬間も、きっと一度や二度はあるはずです。大丈夫、それってあなたがサボっているわけでも、センスがないわけでもありません。Reactの世界には、初心者さんが思わず引っかかってしまう「ちょっとした罠」がいくつか仕掛けられているんです。
今回は、その中でも特に多くの人が最初の数ヶ月で盛大にハマる「propsからstateを初期化するアンチパターン」と、複数の状態がバラバラになってしまう「状態の同期不全」について、おしゃべりするような気持ちでじっくり紐解いていきましょう。
—
1. そもそも `useState` ってどんなもの?(おさらい)
Reactの `useState` は、いわば「コンポーネント専用のメモ帳」です。
ユーザーがボタンを押した回数や、入力フォームに打ち込んだ文字などを、このメモ帳にメモしておくことで、Reactは「あ、データを書き換えたんだな、じゃあ画面を新しくしなきゃ!」と気づいて、画面をパパッと描き直してくれます。
とっても便利なこのメモ帳ですが、使い方を少しだけ間違えると、メモ帳と実際のデータがチグハグになってしまい、アプリが変な動きをするようになります。その代表例を見てみましょう。
—
2. 【アンチパターン】親から受け取ったデータを、そのまま自分のメモ帳の表紙にしてしまう罪
初心者さんがやってしまいがちな、一番有名な罠がこれです。
親コンポーネントから「これ、使ってね」と渡されたデータ(`props`)を、子コンポーネントの `useState` の初期値としてそのままセットしてしまうパターンです。
身近な例え話で考えてみよう
例えば、あなたがカフェの店員さんだとします。店長(親)から「今日のオススメ商品は『カフェラテ』です」とメモを渡されました。
あなたはそれを自分のメモ帳(`state`)に「今日のオススメ:カフェラテ」と書き写しました。
さて、ここからが問題です。
もし途中で、店長が「あ、ごめん、今日のオススメ『抹茶ラテ』に変更ね!」と新しいメモをあなたに渡してきたとします。
このとき、あなたが自分のメモ帳に書いた「カフェラテ」という文字をそのまま見つめ続け、「店長が何と言おうと、私のメモにはカフェラテって書いてあるもん!」と書き換えを拒否してしまったら……?
お店は大混乱ですよね。これが、Reactで起こる「propsからstateを初期化するバグ」の正体です。
実際のコードで見てみよう(ダメな例)
import { useState } from ‘react’;
// 親コンポーネント
function Parent() {
const [userName, setUserName] = useState(‘田中さん’);
return (
親画面
{/ ユーザー名を切り替えるボタン /}
{/ 子コンポーネントに名前を渡す /}
);
}
// 子コンポーネント(【危険】アンチパターン)
function Child({ initialName }) {
// ⚠️ 親から受け取った initialName を state の初期値にしてしまっている!
const [name, setName] = useState(initialName);
return (
子画面
受け取った名前: {name}
{/ 子側で勝手に名前を変える入力欄 /}
setName(e.target.value)}
/>
);
}
このコードを動かすと、どうなるでしょうか?
最初は親から「田中さん」が渡されるので、子画面にも「田中さん」と表示されます。ここまではバッチリです。
しかし、親側のボタンを押して「鈴木さん」に名前を変更しても、子画面の名前は「田中さん」のまま変わりません。
なぜなら、`useState` の初期化はコンポーネントが最初に画面に現れた(マウントされた)その一瞬しか行われないからなんです。親が何を渡そうと、子は一度覚えた初回のデータを頑なに握りしめたままになってしまいます。
—
3. じゃあ、どうすればいいの?(正しい解決策)
この問題をスマートに解決するための鉄則、それは非常にシンプルです。
> 「親からリアルタイムで変化するデータをもらうなら、自分のメモ帳(state)に写すな。親のデータをそのまま(あるいは計算して)表示しろ!」
これを「単一の真実の源(Single Source of Truth)」と呼んだりしますが、要するに「ボスの言うことを素直に聞く(親のpropsをそのまま使う)」のが一番安全ということです。
修正したコード(安心の正しい例)
import { useState } from ‘react’;
function Parent() {
const [userName, setUserName] = useState(‘田中さん’);
return (
親画面
);
}
// 子コンポーネント(【安全】正しい設計)
// stateを作らず、受け取ったpropsをそのまま表示する!
function Child({ name }) {
return (
子画面
現在の名前: {name}
{/ ※もし子側で入力を受け付けたい場合は、親から「変更用の関数」を渡してもらうのがReactの流儀です /}
);
}これなら、親のデータが変われば、子は一瞬で新しい名前に切り替わります。すっきりしていて気持ちがいいですね!
—
4. 「どうしても自分のメモ帳に控えたい!」そんなときの最終手段
「いやいや、初期値として使いたいだけで、そのあとは子コンポーネント独自にバリバリ書き換えたいんだよ!」というケースも、実務ではたまにあります。例えば、ユーザーのプロフィール編集画面などで、初期データをフォームの入力値として読み込み、そこからユーザーに自由に書き換えてもらうような場合です。
そんなときは、以下の2つのアプローチのどちらかを選びます。
1. 「これっきり初期値」であることを名前や `key` で明示する
親から渡されるプロパティ名を `initialName`(初期の名前)とし、本当に最初の1回だけの初期値であることをコード上でハッキリさせます。
2. キー(key)の仕組みを利用して、親のデータが変わったらコンポーネントごと新しく生まれ変わらせる
Reactの `key` 属性が変わると、Reactは「あ、前のコンポーネントは用済みだから消して、新しいのをゼロから作り直そう」と判断します。これを利用して、親のIDなどが変わったタイミングで子を強制リセットするテクニックもあります(※少し応用編です)。
—
5. まとめ:今日からできる心の持ち方
Reactで状態を扱うときは、以下のポイントだけ心に留めておいてください。
- `useState` はコンポーネントの「自分だけのプライベートなメモ帳」
- 親から受け取った `props` をそのまま `useState` の初期値にするのは、情報の同期ズレ(バグ)の温床になるので基本はNG!
- データは大元(親や上位のストア)で一元管理し、子どもはそれをただ表示するだけにすると、アプリがぐっと健やかになる。
最初につまずきやすいポイントだからこそ、ここで「なるほど、親のデータはそのまま見るのが一番安全なんだな」と体感できていれば、これからのReactライフは劇的にスムーズになりますよ。
もしまたコードを書いていて「あれ、なんか画面が連動しないぞ…?」と迷ったら、いつでもこのページに戻ってきてくださいね。あなたのReact学習を、心から応援しています!

コメント