はじめに
Reactの公式チュートリアルをやっていてハマったところを解説します。
問題
state変数にオブジェクトが入っているとします。
const [position, setPosition] = useState({ x: 0, y: 0 });
position
を更新したいとき、次のように書いてはいけません。
position.x = 1;
position.y = 2;
以下もダメです。
position.x = 1;
position.y = 2;
setPosition(position);
理由を順番に説明していきます。
state変数の変更はset関数を使いましょう
まず1つ目のコードがダメな理由を説明します。
position.x = 1;
position.y = 2;
前提として、state変数が数字だろうと文字列だろうオブジェクトだろうと、set関数以外で変更してはいけません。
ただ、数字や文字列の場合はプリミティブな値なので、それ自体を変化させることは、やろうとしてもできません。
え? こうすれば数字でも変えられるじゃん? と最初は思いましたが、、
const [x, setX] = useState(0);
setX(5);
このコードはx
というstate変数の値を0から5に変更しているだけで、0という値自体を5に変えているわけではないです。なので問題ありません。
しかしながら、state変数の値がオブジェクトの場合は、オブジェクト自体の値を書き換えることができてしまいます1。
例えば、以下ではposition
というstate変数自体を変化させています。
const [position, setPosition] = useState({ x: 0, y: 0 });
position.x = 1;
position.y = 2;
これの何が問題かというと、Reactがこの変更を検知できないことです。
内部的にはposition
の値は確かに変更されていますが、Reactがそれを検知できないため再レンダリングされず、画面上なんの変更もされていないように見えてしまいます。
解決策
set関数を使用すればReactに変更を検知してもらえます。
新しいオブジェクト{ x: 1, y: 2 }
を作成し、setPosition
に渡せば、無事に再レンダリングされます。
setPosition({ x: 1, y: 2 });
疑問
ここで疑問に思いました。
わざわざ新しいオブジェクトなんて作らなくても、set関数にposition
を渡せばいいじゃないかと。
冒頭に書いた2つ目のコードです。
position.x = 1;
position.y = 2;
setPosition(position);
しかしながら、これでも画面上position
の値は更新されません。
set関数は、state変数の値が変化したときだけ実行されるからです。
今回の場合、オブジェクトであるposition
のx
プロパティの値が変わっただけなので、position
自体の変更とはみなされず、setPosition
は実行されません。
解決策
なので、やっぱり新しいオブジェクトを作って、set関数に渡す必要があります。
setPosition({ x: 1, y: 2 });
終わりに
2つ目のコードがなぜ上手くいかないのかがわからなくて調べて、結果的に理解が深まったので良かったです。
参考
-
今回は扱いませんでしたが、配列もオブジェクトと同じくミュータブルなので同じ問題が発生します ↩