こんにちは!フロントエンドの現場を渡り歩いているチーフアーキテクトです。
日々、多くの開発者から「Reactの状態管理でどうしてもハマってしまう……」という相談を受けます。
Reactの `useState`、本当に便利ですよね。画面のデータをポイッと放り込んでおけば、勝手に画面を書き換えてくれる魔法の箱のような存在です。でも、この魔法、たまに意図しないところで牙を剥くんです。
例えば、「フォームの入力途中で別のデータに切り替えたのに、前の入力データが残っちゃった……!」なんてバグに遭遇したことはありませんか?
「あれ? ちゃんとリセットしたはずなのに!」と、夜中に冷や汗をかいた経験、きっと一度や二度ではないはず。
大丈夫ですよ、安心してください。それはあなたのコードが下手なわけでも、Reactに嫌われているわけでもありません。単にReactの「お片付けのルール」を知らなかっただけなんです。
今回は、そんな絶望を華麗に解決する、知る人ぞ知る(でも実務では超頻出の)裏技……ではなく王道テクニック、「`key`属性を利用した状態の強制リセット」について、身近な例えを交えながら優しく紐解いていきましょう!
—
1. なぜ `useState` のデータが残ってしまうのか?
まずは、Reactがどうやって画面を見ているのか、その「おつむの仕組み」を覗いてみましょう。
イメージしてみてください。あなたは「カフェの注文カウンター」の店員さんです。
お客さんが次々とやってきて、メモ用紙(`useState`)に「ブレンドコーヒー、サイズはM」と書いてあなたに渡します。
ここで、同じ席(同じコンポーネントの位置)に、別のお客さんが座ったとします。
もし、あなたが「さっきのメモ用紙をそのまま使い回しちゃえ」としたらどうでしょう? 次のお客さんが「紅茶を一つ」と言っているのに、メモには「コーヒーM」と残っていたら大混乱ですよね。
Reactもこれと全く同じことをやっています。
Reactは、コンポーネントが画面の「同じ場所(ツリーの同じ階層)」にあるとき、「あ、さっきと同じコンポーネントね。前の記憶を引き継いでおこう」と親切心から前回の `useState` の中身を保持し続けちゃうんです。
これが、私たちが意図しないタイミングで古いデータが残ってしまう原因です。親切心があだになるってやつですね。
—
2. 魔法の杖:`key`属性で「別人格」に生まれ変わらせる
「じゃあ、新しいお客さんが来たら、一度メモ用紙をビリビリに破り捨てて、まっさらな新品のメモを渡したい!」
そんなときに登場するのが、今回の主役である `key`属性 です。
Reactにおいて、`key` は単なる「リストの警告を消すためのおまじない」ではありません。
「keyの値が変わったら、Reactはこのコンポーネントを完全に捨て去り(アンマウント)、まっさらな状態でゼロから作り直す(再マウントする)」 という、極めて強力なスイッチなのです。
例えるなら、`key` はコンポーネントの「戸籍番号」や「別人格ID」のようなもの。
IDが変われば、Reactは「おっ、全く新しい人が来たんだな!」と認識し、過去の記憶(`useState` の状態)をきれいさっぱり忘れて、初期状態からやり直してくれます。
—
3. 実践!コードでその動きを見てみよう
百聞は一見に如かず。実際にコードを書いて、その動きを体感してみましょう。
今回は、ユーザーのプロフィール編集画面をイメージしてください。ユーザーを切り替えたときに、入力中のテキストがどうなるかを試してみます。
以下のコードを、あなたのエディタにそのまま貼り付けて動かしてみてくださいね。
import React, { useState } from ‘react’;
// 【子コンポーネント】ユーザーのプロフィール入力フォーム
function ProfileForm({ user }) {
// ユーザー名を入力するためのstate
// ★ここに「前のユーザーの入力内容」が残ってしまう罠が潜んでいます
const [inputName, setInputName] = useState(”);
return (
現在編集中のユーザー: {user.name}
※inputに何か文字を入力した状態で、上の「ユーザーを切り替える」ボタンを押してみてください。
現在の入力値: {inputName || ‘(未入力)’}
);
}
// 【親コンポーネント】
export default function App() {
// 選択中のユーザーID(1 または 2)
const [selectedUserId, setSelectedUserId] = useState(1);
// ダミーのユーザーデータ
const users = {
1: { id: 1, name: ‘山田 太郎’ },
2: { id: 2, name: ‘佐藤 花子’ },
};
const currentUser = users[selectedUserId];
return (
key属性による状態リセットの実験場
{/ ユーザーを切り替えるボタン /}
{/ — パターンA:keyをつけていない場合(状態が残ってしまう) — /}
{/
/}
{/ — パターンB:keyをつけている場合(ユーザーが変わるたびにリセットされる) — /}
{/ user.idが変わるとkeyも変わるため、コンポーネントが丸ごと新しく生まれ変わります /}
);
}
動きを試してみる手順:
1. 上記のコードを動かしたら、まずは「山田 太郎」さんの状態で入力欄に「こんにちは!」と打ち込んでみてください。
2. その入力した文字を残したまま、「ユーザーを切り替える」ボタンを押して「佐藤 花子」さんに切り替えてみます。
3. パターンB(`key={currentUser.id}` を渡している状態)であれば、入力欄が綺麗さっぱり空っぽ(初期状態)に戻っているはずです!
もしここで `key` を外してしまうと(コメントアウトを入れ替えてみてください)、佐藤さんに切り替えた瞬間にもかかわらず、入力欄には先ほどの「こんにちは!」が残ったままになってしまいます。これでは大事故につながりかねませんよね。
—
4. チーフアーキテクトからの実務アドバイス:使うときの注意点
この `key` による強制リセット、めちゃくちゃ便利で私も現場でよく使いますが、一つだけ注意してほしい「諸刃の剣」な一面もあります。
それは、「コンポーネントが完全にゼロから作り直される(再マウントされる)ため、パフォーマンスに少しだけコストがかかる」ということです。
- 軽いコンポーネントや、モーダルの開閉、タブの切り替えなど:
ガンガン `key` を使って状態をきれいに保ちましょう!バグを生むよりよっぽど健全です。
- ものすごく重い処理を含むコンポーネントや、巨大なリスト:
毎回の再マウントが重荷になることがあるので、素直に親コンポーネント側で `useEffect` やハンドラーを使って `useState` の値を明示的にリセット(`setInputValue(”)` など)してあげる方がスマートな場合もあります。
適材適所。これぞプロの技です。
—
おわりに
Reactを使っていると、画面とデータの同期ズレに悩まされることは本当によくあります。「あれ、なんでデータが残っちゃうんだろう?」と迷ったときは、ぜひ今日お話しした 「コンポーネントのパスポート(key)を変えて、別人格として生まれ変わらせる」 というアプローチを思い出してみてください。
あなたのReactライフが、少しでもストレスフリーで楽しいものになりますように。
それでは、また次の現場でお会いしましょう!チーフアーキテクトでした!

コメント