◆ コンポーネントを新しいのに置き換えたいのに再利用される
◆ state が引き継がれる
◆ props を元に自力で state クリア必要
◆ 変化の検知だけで一苦労

state をクリアしたい

WebComponents ではない React 系コンポーネントを使ってみましたがやっぱりクリアのしづらさがあります
WebComponents だと要素を作り直せばいいのですが React 系だと内部で管理されてるから同じコンポーネントだと使い回されてクリアされないのですよね

複数のコンポーネントから状況に応じてどれかを表示するというシーンはときどきあります
ルーターとかタブとかです

そこで同じ種類のコンポーネントでプロパティの指定が違うものがあった場合 それらを切り替えたときにはコンポーネントの種類は同じです
そういう場合に React では今あるコンポーネントのインスタンスと DOM が引き続き使われます
componentDidMount なども呼び出されません
その結果 state はリセットされずに引き継がれてしまいます

いったん別の種類のコンポーネントに切り替えてから戻ってくると 別のコンポーネントに切り替えたときにインスタンスは削除されて 戻したときに新しくインスタンスが作られてます
この場合のインスタンスは別ものなので state もリセットされています


タブなどでの切り替えに限らず ID を受け取って その ID の情報を表示するコンポーネントがあったとき 表示する ID が変わったら state もクリアしたいというだけでも困ります
今のインスタンスを捨てて新規に作ってくれればいいのに それが簡単にできないのですよね
WebComponents で作ってれば新しく要素作って replaceWith で済むので簡単なのですけど……
新しく作るのにコスト大きいなら setter 用意しておいて更新されたら reset 処理を実行することもできますし DOM 操作するだけあって自由度が高いです

React の場合 props は自分で定義できるオブジェクトじゃなく Component の直接のプロパティでもないので setter は作れないはずです
調べてみると componentWillReceiveProps を使えば新しい props と以前の props を比較して処理をできるみたいですが 今ではこれも deprecated です
それに React 使うなら全部関数コンポーネントにしたいので class コンポーネントは避けたいんですよね

一応やってみた

この問題は困る人多いんじゃないかと思ってググってみるとあるにはありました
https://hackernoon.com/replacing-componentwillreceiveprops-with-getderivedstatefromprops-c3956f7ce607

後半は実際の例みたいなので複雑になってるので 2 つめのコードブロックを見ればわかりやすいです
getDerivedStateFromProps を使って props の変化をチェックしています

componentWillReceiveProps が deprecated になって 代替は getDerivedStateFromProps という情報は知ってはいたのですが static なので コンポーネントの状態は取得できず 引数として受け取れるのは新しい props と state です
props の変化の検知には使えないと思って諦めていましたがこれを使っています

componentDidUpdate のタイミングで state に以前の props の値を保存しています
これによって props と state から変化があったことを検出できるというわけです
ただ できはしますがわかりづらくてあんまり使いたい方法ではありません

これなら毎回 state に props を入れるより最初から state に Component のインスタンスの参照をもたせておいたほうが個人的にはスッキリして好きです
まぁ 「state とは何か」 みたいなこと言い出すと Component のインスタンスの参照が入ってるのはおかしいとかいう考えの人もいそうですけど

class ExampleComponent extends Component {
state = {
count: 0,
instance: this,
}

static getDerivedStateFromProps(props, state){
if (props.id !== state.instance.props.id) {
return { count: 0 }
}
}

// 略
}

一応これで id が変化したタイミングでコンポーネントの count を 0 にクリアすることができました

ただこれを関数コンポーネントにするのは難しそうです
関数内のフックで毎回同じオブジェクトを取得するようにして そのプロパティに以前の props を毎回セットするあたりでしょうか
インスタンスがない分 クラスコンポーネントみたいに this を保存しておけないのが扱いづらいところです

<!doctype html>
<meta charset="utf-8" />

<script type="module">
import { html, render, useState, useMemo } from "https://unpkg.com/htm/preact/standalone.module.js"

const App = props => {
const [id, setId] = useState(0)

return html`
<div>
<button onclick=${() => setId(0)}>0</button>
<button onclick=${() => setId(1)}>1</button>
<button onclick=${() => setId(2)}>2</button>
</div>
<${CounterPage} id=${id} />
`
}

const CounterPage = props => {
const [count, setCount] = useState(0)
const memo = useMemo(() => ({ props: {} }), [])

// reset count when id is changed
if (props.id !== memo.props.id) {
setCount(0)
}

memo.props = props

return html`
<p>id is ${props.id}</p>
<button onclick=${() => setCount(count + 1)}>${count}</button>
`
}

render(
html`<${App} />`,
document.getElementById("root")
)
</script>

<div id="root"></div>

一応動く形にはなりました
ただ やっぱり「表示対象を変えたら状態もリセットしたい」というだけのために わかりづらいロジックが入ってしまうのはイマイチ感があるんですよね

state 無いほうがいいかも

こういう面倒さがあると結局コンポーネントにして状態を持つよりも 状態はすべて外側で管理のほうが楽な気がしてきます
コンポーネントは引数から HTML 構造を返すだけで毎回ルートから全体の HTML 構造を見て差分更新です
これまで lit-html や hyperhtml で作ってたときは これでしたが特に困ってません

React なら Redux がありますが非同期とか面倒も多いらしいので lit-html などのときと同じで自分で簡単なものを作れば十分かもです