イベントハンドラーやイベントリスナーでReactを扱う時に注意すること
React の State を扱う関数をイベントリスナー(EventListener)として要素やオブジェクトに登録する際に値の更新ができない場合は、useRef フックを使いましょう。
問題点
React では用途が少ないかもしれませんが、要素やオブジェクトに対してイベントリスナーとして State を更新する関数を登録したいケースがあります。特にありそうなのはビデオ通話や音声通話、テキストチャットなど WebSocket を利用するようなアプリケーションを構築するケースでしょう。
// この例は動きません。
var socket = io.connect();
cont[(messages, setMessages)] = React.useState([]);
socket.on("data", function (data) {
setMessages(messages.concat(data));
});
イベントリスナーとして登録する関数で State を扱う場合、それが更新されないということが発生します。上記のケースはデータを受け取ったとき、メッセージを追記していくことを期待していますが、この場合常に最新のメッセージしか保持されません。
これは困ったことです。
原因
これが起こる原因はイベントリスナーに登録した関数が登録したときの状態の State を保持してしまうからです。
data イベントが発生した時、そのイベントリスナーの関数内の State は常に[]となっていて空の配列になります。なのでデータの追加を何度も何度も行っても、最新のデータしか入らない結果になります。
解決
これを解決する方法として useRef を使う方法があります。
上記の例の場合、以下のように修正できます。
var socket = io.connect();
cont[(messages, _setMessages)] = React.useState([]);
const messagesRef = React.useRef(messages);
const setMessages = (data) => {
messagesRef.current = data;
_setMessages(data);
};
socket.on("data", function (data) {
setMessages(messagesRef.current.concat(data));
});
インスタント定数として扱うことでその時点の最新のステートにアクセスできます。 さらに ref は値が更新されても通知しないため、再レンダーされないという問題に対処するために ref の値を更新すると同時に state の更新も行っています。
この問題についての詳しい例については、以下の記事が詳しいです。
より良い React ライフを! それでは!