コンポーネントのkeyにインデックスを使ってしまいがち
map関数で、returnで返すコンポーネントにユニークなkeyを与えると思いますが、
その時ついつい何も考えずにmap関数の第二引数であるインデックスを使ってしまいがちです。
例)
users.map((user, index) => (
<UserDetail key={index} />
))
こういった書き方は基本推奨されてません。
公式にもそう記載されてます。
ただ、keyをつけないとwarningが発生するため、回避策としてついつい楽をしてindexを使ってしまうことが多いと思います。(僕も結構やっちゃってました)
インデックスを使うべきではない理由
中身が変わってないのに再更新かかってしまう
以下公式ドキュメントの一文を抜粋
key として配列のインデックスが使用されている場合、並べ替えはコンポーネントの状態に関しても問題を起こすことがあります。コンポーネントのインスタンスは key に基づいて更新、再利用されます。インデックスが key の場合、要素の移動はインデックスの変更を伴います。結果として、非制御の入力などに対するコンポーネントの状態が混乱し、予期せぬ形で更新されてしまうことがあります。
ここに記載の通り、インデックスをkeyにすると並び替え処理やフィルター処理をした時に、
各コンポーネントに紐づくインデックスが変わることになるため、結果状態は変わってないのににコンポーネントが更新されてしまいます。
予測不能なバグの発生を起こす可能性がある
こちらに簡単な例を作成されおります。
インデックスのように順番をkeyにしてしまうと、例のように要素が増えたときに意図しない挙動を起こしてしまいます。
その他、こういうのも好ましくない
毎回関数で値を生成してkeyに使う
不安定な値(Math.random()とか毎回関数を実行してkeyを生成するようにする)をkeyに使うのも推奨されてません。
まあ、あまりそういうことをすることはあまりないと思いますが、パフォーマンスの低下にも繋がるので気をつけましょう。
keyを子コンポーネントで使用する
keyはReact側で何か状態が変わったのかを推測するために使用するためのものなので、このkeyを子コンポーネント配下で使用するのは好ましくないようです。
もし使いたい場合は別途値を定義して渡すようにしましょう。
<UserDetail key={user.id} userId={user.id} />。
正しい書き方
データ内にあるユニークな値を使おう
users.map((user) => (
<UserDetail key={user.id} /> // 各データに入っているユニークな値を使用
))
ループ処理で回す配列内のデータに入っているユニークな値を使うようにしましょう。
恐らくAPIから取得したデータには各データにidがあると思うので、それをkeyに使うのがベストです。
もし配列データにユニークな値がなかった場合
keyで使うために、新しくユニークなプロパティを作成しましょう。
【以下参考】
最後に
こういった状況の時はインデックスを使っても大丈夫
以下の状況の時はインデックスをkeyに使っても問題ないです。
- 配列内のデータが計算とかしないとき
- もともと配列データにユニークなIDがないとき・ユニークなIDを作成するロジックを作るのが面倒な時
- 並び替えやフィルター処理をしない時
ただ、基本的にインデックスを使わずにデータ内のidとかユニークな値を使うことを心掛けた方が良いかと思います。
ESlintを使用してkeyにインデックスを使用してないか監視
また、ESLintのプラグインでkeyにインデックスを使用してる時にwarningを出してくれるので、導入するのオススメです。