はじめに
最近音楽ストリーミングアプリの開発をやっています。
開発中、どうしても親コンポネントから子コンポネントに与えた prop を変えても変化が写ってないのところはかなり珍しかった。
ネットで調べみたら、原因は理解できました。
説明
まずはサンプルコードを見てみましょう。
import { useState } from 'react';
// 親コンポネント
const ParentComponent = () => {
const [data, setData] = useState(0);
return (
<div>
<ChildComponent prop={data} />
<button onClick={() => setData(data => data + 1)}>Add +1</button>
</div>
)
}
// 子のコンポネント
const ChildComponent = ({ prop }: { prop: number }) => {
return (
<div>
Prop: {prop}
</div>
)
}
親コンポネントでボタン押したら、data は変わっているから新たにChildComponentの prop の値は変わるでしょう?そういう考えたら、常識的にChildComponentの Prop 表示も変わると思うでしょう。でも、実際は変わらないです。
理由は、コンポネントのstateが変わるだけでそのコンポネントは再びレンダーされます。あくまでprop はコンポネントのstate じゃないから、再レンダーも行わないです。
対策方法は2つあります。
対策方法1
一番単純な対策法は、子コンポネントのkeyでpropを設定する。
コンポネントのkeyを変えれば、必ずコンポネントを再レンダーされますから。
import { useState } from 'react';
// 親コンポネント
const ParentComponent = () => {
const [data, setData] = useState(0);
return (
<div>
<ChildComponent key={data} prop={data} />
<button onClick={() => setData(data => data + 1)}>Add +1</button>
</div>
)
}
// 子のコンポネント
const ChildComponent = ({ prop }: { prop: number }) => {
return (
<div>
Prop: {prop}
</div>
)
}
対策方法2
この方法はちょっと見づらくて分かりづらいときがする。
メモする為書いておきますが、あまり勧めません。
propはコンポネント内でstateとして保管して、親コンポネントから受けたpropを変えたら、useEffectでstateを更新すればコンポネントは再レンダーされます。
"use client"
import { useState, useEffect } from 'react';
// 親コンポネント
const ParentComponent = () => {
const [data, setData] = useState(0);
return (
<div>
<ChildComponent prop={data} />
<button onClick={() => setData(data => data + 1)}>Add +1</button>
</div>
)
}
// 子のコンポネント
const ChildComponent = ({ prop }: { prop: number }) => {
const [childData, setChildData] = useState(prop);
useEffect(() => {
setChildData(prop);
}, [prop])
return (
<div>
Prop: {childData}
</div>
)
}
見にくいでしょう?
そして、この方法にはもう1つの問題がある。
useEffectとuseStateはクライントコンポネントしか使えないですので、Next.js などフレムワークで子コンポネントにサーバーコンポネントは使えなくなる。つまり、子コンポネントで非同期処理が必要になったら、その処理もuseEffectを使わず実行できなくなる。
宝個人的に対策法1を利用するのが楽だと思います。
最後に
React でpropを使うのが基礎ですが、この問題と遭うまでこんなことも起きられると思わなかった。
だからこそ、どれほど自信や経験持っても、自分でプロジェクトを作るのが大事ですよね。