【入門編】 propsからstateへの同期パターン – React実践ガイド

こんにちは!Reactの学習、楽しく進めていますか?
画面がスイスイ動くようになってくると、「おっ、私にもできるぞ!」というワクワク感がたまりませんよね。

でも、Reactを触り始めて多くの人が「あれ……?」と最初に立ち止まる、ちょっとした魔物のような罠があるんです。それが今回お話しする「propsの変更をそのままstateに同期させようとして、頭がこんがーる問題」です。

大丈夫ですよ、最初はみんなここで盛大にハマります。私も昔、何度も夜を徹してコードと睨めっこしました。
今回は、なぜこれが「やっちゃいけないアンチパターン」なのか、そしてどうすればスマートに解決できるのかを、身近な例えを交えながら優しく解きほぐしていきましょう!

—

1. カフェの注文カウンターで考えてみよう

まずはイメージしやすいように、身近な例え話をさせてください。

あなたは今、大人気のカフェでレジ打ちのアルバイトをしています。
お客さん(親コンポーネント)がレジにやってきて、「アイスコーヒーを一つ!」と注文してくれました(これが props です)。

あなたはそれを聞いて、手元のメモ用紙(これが子コンポーネントの state)に「アイスコーヒー」と書き込みました。ここまでは順調です。

やりがちな「大惨事」の始まり

ところが、お客さんが行列の途中で急に気を変わり、「あ、やっぱホットティーにして!」と言い出しました(propsの変更)。

ここで、もしあなたが「お客さんが注文を変えたんだから、私のメモ用紙も勝手に書き換えなきゃ!」と、メモ用紙の管理を自分で頑張ろうとすると……どうなるでしょう?
お客さんの口頭での変更と、あなたがメモを書き直すタイミング、そしてすでに作り始めていたドリンクの指示がごちゃ混ぜになり、レジカウンターは大パニックです。

Reactの世界でもこれと全く同じことが起きます。
「親から渡されたデータ(props)の変化を監視して、子自身のメモ(state)を無理やり書き換えようとする」。これが、バグを生む最大の原因なんです。

—

2. なぜ「propsをstateにコピーする」とダメなの?

コードでよく見かける、こんな書き方を思い出してみてください(あるいは、まさに今書こうとしていませんか?)。

// ❌ やりがちなアンチパターン
function ChildComponent({ initialCount }) {
// propsを初期値としてstateに入れる
const [count, setCount] = useState(initialCount);

// propsが変わったときにstateも無理やり同期させようとする(useEffectの悪用)
useEffect(() => {
setCount(initialCount);
}, [initialCount]);

return

現在のカウント: {count}

;
}

一見すると、「親の数字が変わったら子も連動して変わりそう」で良さそうに見えますよね。
でも、これには大きな問題が2つあります。

1. 「一人の人間が2つの財布を持っている」状態になる
データの情報源(Single Source of Truth)が、親の持つ `initialCount` と、子の持つ `count` の2つに分裂してしまいます。「どっちが今の正しい数字なの?」とReactも私たちも迷子になってしまいます。
2. 非同期のタイミングズレでバグる
Reactの状態更新(useState)は非同期で行われます。そのため、「propsが変わった!すぐstateを更新しなきゃ!」という焦りが、予期せぬ無限ループや古いデータの表示(ステートのズレ)を引き起こすんです。

「親からデータをもらうなら、子でそれを自分の勝手な持ち物にしない」。これがReactの鉄則です。

—

3. じゃあどうするの?解決策①:計算できるものは「その場で計算する」

もし、子コンポーネントで表示したいデータが、親からもらったpropsをもとにパパッと計算できるものなら、そもそもstate(専用のメモ用紙)を作る必要すらないんです。

カフェの例で言えば、メモ用紙にわざわざ書き写さなくても、お客さんの注文票をそのまま見ればいいよね、というお話です。

💡 解決コード例

// ⭕️ 正しいアプローチ:stateを持たず、propsをそのまま使う
function PriceTag({ unitPrice, quantity }) {
// stateを使わず、propsからその場で合計金額を計算する
// これなら「同期ズレ」なんて起きようがありません!
const totalPrice = unitPrice quantity;

return (

単価: {unitPrice}円

数量: {quantity}個

合計金額: {totalPrice}円

);
}

「stateを持たないなんて不安……」と思うかもしれませんが、「変化するデータをstateに入れなくていいなら、入れないのが一番安全で美しいコード」です。これを覚えておいてくださいね。

—

4. 解決策②:「どうしてもリセットしたい」ときは `key` を使う

「いやいや、子コンポーネントが自分自身でフォームの入力途中などの内部状態(state)を持っていて、親から新しいIDやデータが渡されたら、初期状態からきれいにリセットしてほしいんだ!」

そんなシーンもありますよね。ユーザーのプロフィール編集画面などを想像してください。別の人に切り替えた瞬間、前の人の入力データが残っていたら大惨事です。

ここで先ほどの `useEffect` で無理やり同期させるのではなく、Reactの奥義「key属性の変更によるコンポーネントの作り直し(マウントし直し)」を使います。

Reactは、要素の `key` が変わると、「あ、中身が全然違う新しいパーツに変わったんだな」と認識し、古いパーツを一度きれいに消して、新しいパーツをゼロから作り直してくれます。

💡 解決コード例

import { useState } from ‘react’;

// 子コンポーネント:自分の中にテキストの入力状態(state)を持っている
function ProfileEditor({ userId, defaultName }) {
// propsの defaultName は「あくまで初期値」として受け取る
const [name, setName] = useState(defaultName);

return (

ユーザーID: {userId}

setName(e.target.value)}
className=”border p-1 rounded mt-2″
/>

);
}

// 親コンポーネント
function ParentApp() {
const [currentUserId, setCurrentUserId] = useState(1);

// ユーザーを切り替えるボタンのデータ
const users = [
{ id: 1, name: ‘田中さん’ },
{ id: 2, name: ‘佐藤さん’ },
];

return (


{/
★ここがポイント!
keyに currentUserId を渡すことで、IDが変わった瞬間に
ProfileEditor ごとキレイに破棄され、新しい初期値で生まれ変わります。
/}

);
}

この方法を使うと、`useEffect` でこねくり回さなくても、Reactのライフサイクルの仕組みに自然に乗っ取って、安全に初期化を行うことができます。初めてこれを知ったときは、私も「なるほど、こうやってコンポーネントを使い捨てるのか!」と目からウロコが落ちました。

—

最後に:つまずいても、一歩ずつ進めば大丈夫

Reactの状態管理は、最初は本当に頭が混乱します。
「あれ?なんでさっきの値にならないんだ?」と手が止まる日もあるでしょう。でも、それはあなたがしっかりとコードと向き合っている証拠です。

もしまた「propsの変更をstateに反映させなきゃいけない気がする……」と思ったら、今日のこの記事をそっと思い出してみてください。

1. そもそも計算で求められないか?(stateを削る)
2. リセットしたいだけなら、`key` でコンポーネントごと作り直せないか?

この2つの引き出しを持っていれば、大抵のUIの悩みは綺麗に解決できます。
あなたのReactライフが、少しでも軽やかで楽しいものになりますように。一緒に一歩ずつ進んでいきましょう!

コメント

タイトルとURLをコピーしました