こんにちは、Yuiです。
今回、ReactでuseStateを使ってモーダルの開閉部分の実装をしてたのですが、うまくレンダリングされず、詰まってしまったので覚書として残します。
問題となった部分
モーダルコンポーネントを呼び出すためのページでボタンを押してもモーダルが開かなかった
該当コード
export const Table: React.FC<Props> = (props: Props) => {
//省略
const [isDialogOpen, setIsDialogOpen] = useState(false)
const sth = useMemo(() => {
//省略
return (
<div>
{isDialogOpen ? (
<button onClick={() => setIsDialogOpen(false)}>ボタン</button>
) : (
<button onClick={() => setIsDialogOpen(true)}>ボタン</button>
)}
<OpenModal
isOpen={isDialogOpen}
onClose={() => setIsDialogOpen(false)}
/>
</div>
)
})
}
OpenModal
コンポーネントは別ファイルにあり、isDialogOpen
のtrue/falseの切り替えでモーダルを表示出来るようにして呼び出す作りになっている。
が、何度やっても値がfalseのままで変わらない。
#useStateが値をすぐに変更してくれない問題
今回、ボタンを押してもisDialogOpen
の値はfalseのままだったのだが、ページを再リロードすれば値が切り替わることがわかった。
つまり、今回はレンダリングがうまくできていないことが原因かもしれないという仮説にたどり着いた。
そこで色々と調べるとどうやらuseStateメソッドが変更をすぐに反映しないことはあるらしい・・?(参考:useState setメソッドが変更をすぐに反映しない)
render関数で呼び出してみる
[React]setState は state の変更リクエストを出すだけという記事を読んで公式ドキュメントを確認すると、どうやらrender関数を使って呼び出せばいいっぽい。ということでやってみたがまだ反映されない・・。
そこで、もしかしてuseMemoが原因か?
ということでuseMemoについて調べてみると、どうやら第二引数がない場合は第一引数で受け取った値をキャッシュとして残してしまうため、レンダリングされないらしい。
もちろんその分一度レンダリングされればキャッシュとして残した値を表示するため、表示速度が速くなるという利点もあるのだが、再レンダリングされないのは盲点だった。
クラスコンポーネントで書いてみる
私が個人的に関数コンポーネントの書き方があまり良くわかっていなかったことと、モーダルで調べたら結構クラスコンポーネントで書いてある人が多かったのでとりあえずクラスコンポーネントで書き直してみた。
export class Table extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
isOpen: false,
isDialogOpen: false,
}
}
//省略
render() {
return (
<div>
<button onClick={this.openDialog}>ボタン</button>
<OpenModal
isOpen={this.state.isDialogOpen}
onClose={this.closeDialog}
/>
</div>
)
}
private openDialog = () => {
this.setState({ isDialogOpen: true })
}
private closeDialog = () => {
this.setState({ isDialogOpen: false })
}
}
これだと動いた。
ただ、今回既存コードの改修でコードを書いていたためクラスコンポーネントで書き換えてもいいか先方に確認してみる。
結果ダメと言われた・・ORL
(これは私の勉強不足だが、現状Reactでは関数コンポーネント+Hooksの書き方が推奨なのね・・)
というわけでなんとか関数コンポーネントで書きたい。また、既存コードで結構useMemoを使って整えられている部分もあったため、useMemoも使いたい。
#解決策
長くなったが、解決策は非常にシンプル。
useMemoに第二引数を入れれば第二引数の値が変わった段階で再レンダリングされる。
というわけで下記。
export const Table: React.FC<Props> = (props: Props) => {
//省略
const [isDialogOpen, setIsDialogOpen] = useState(false)
const sth = useMemo(() => {
//省略
return (
<div>
{isDialogOpen ? (
<button onClick={() => setIsDialogOpen(false)}>ボタン</button>
) : (
<button onClick={() => setIsDialogOpen(true)}>ボタン</button>
)}
<OpenModal
isOpen={isDialogOpen}
onClose={() => setIsDialogOpen(false)}
/>
</div>
)
//下記に第二引数を指定
},[isDialogOpen])
}
こうすると無事問題なく表示された!!
長かった・・。
#感想
今回は急にReactの案件に参画することになったのでワケワカラン状態で書いてたけど、やっぱりちゃんと勉強しようと思った。(当たり前)