はじめに
refを使っていますか?
私はReactを触り始めて、1ヶ月ぐらいでrefを教えていただきました。
知識としては知っていましたが、実務で実際に書いてみてrefとstateの違いと、refを使ってはいけない具体例を経験することができたので、共有します。
プログラミング初心者やReactの初学者の方の参考になれば嬉しいです。
自己紹介
私は2023年9月1日にフロントエンドエンジニアとして自社開発のSaaS企業に入社しました。
未経験独学で、薬剤師からの転職です。
詳しくはこちらをご覧ください。
具体例で説明
背景
WEBのサービスサイトの問い合わせフォームを実装しています。
form要素を使って、問い合わせ送信ボタンを実装しました。
追加要件として、送信中は何度もリクエストを送れないようにするために、一度送信ボタンを押したらボタンを非活性にするという実装をすることになりました。
設計
- 送信ボタンを一度押したら非活性にする
- button要素のdisabled属性をtrueにする
- 判定する条件に
isSending
を使う
実装
追加要件を先輩から伝えられた時に、「すでにisSending
を使った同じような実装があるからそれを参考にしてみて」と言われました。
ソースコードを見てみると、 const isSending = useRef(false);
と定義されていました。
なるほど!これを使えばいいんですね👍と何も考えずに実装してみました。
import React, { useRef, FC } from 'react';
const FormComponent: FC = () => {
const isSending = useRef(false);
const handleSubmit = () => {
isSending.current = true;
// ここでfetchする
};
return(
<form onSubmit={handleSubmit}>
{/* {問い合わせフォームの内容は省略} */}
<button disabled={isSending.current}>送信</button>
</form>
);
};
export default FormComponent;
まずは、参考にする実装と同じようにisSending
をuseRef
を使って宣言します。初期値はfalse
です。
送信ボタンを押した時に実行されるhandleSubmit関数
の中で、isSending
の値をtrue
に更新します。
そして、button要素のdisabledプロパティでisSending.current
を読み取リます。isSending.current
がtrue
ならbutton要素は非活性になります。
それではどのような動きになるのかを確認します。
ん???何かがおかしい…
一度送信ボタンを押してもボタンは非活性になっていません。
もう一度送信ボタンを押すと非活性になります。
これは一体何が起きているのでしょうか?
これがReact公式ドキュメントにも落とし穴として書かれている、レンダー中に ref.current の値を読み取ったり書き込んだりしないでください。 にまんまとはまってしまった結果です。
refとstateの違い
こちらのReact公式ドキュメントでとても分かりやすく解説されています。
refとstateの大きな違いは、変更した時にコンポーネントが再レンダリングされるかどうかです。
refの場合は、値が変更されてもコンポーネントは再レンダリングされません。再レンダリングされないということは、値を変更したとても画面に反映されないということです。
そのため、レンダー中にcurrentの値を読み取る(または書き込む)べきではない とされています。
私が書いた例で何が起こっていたのかを考えてみます。
- 送信ボタンを押した時、currentの値はtrueに変更される
- しかし、refの値が変更されてもコンポーネントは再レンダリングされないため、ボタンが非活性になるという画面の変化は起こらない
- 再度押した時に非活性になったのは、おそらく他の要因でコンポーネントが再レンダリングされてrefの変更が画面に反映されたから
refの値を使って画面を変更しようとすると、こういうことになるんですね😭
stateを使って書き直しましょう。
修正
import React, { useState, FC } from 'react';
const FormComponent: FC = () => {
const [isSending, setIsSending] = useState(false);
const handleSubmit = () => {
setIsSending(true);
// ここでfetchする
};
return(
<form onSubmit={handleSubmit}>
{/* {問い合わせフォームの内容は省略} */}
<button disabled={isSending}>送信</button>
</form>
);
};
export default FormComponent;
これで送信ボタンを押した時にisSending
のstateが更新され、ボタンが非活性になる変更が画面に反映されるようになりました。
refの注意点
最後にrefを使う注意点を自分の言葉でまとめます。
- refをレンダー中に使うな!!
- refはstateと違って値が更新されても再レンダリングされない
- UIの変更に反映されない可能性がある
- return文の中にrefを使った判定を入れるのは危険だからstate使う!!
終わりに
refについて何にも分かっていなかったと痛感しました…
知識をつけても、痛い目をみないと身につきませんね😭
ガンガン失敗して使いこなせるようにしていきたいです。