@keitafujimoto0917

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

Reactプロジェクトで状態更新後の値がすぐに反映されず、古いデータが使われてしまう問題

現在、Reactで業務用Webアプリケーションを開発しています。

開発中に、stateを更新した直後にその値を参照すると、更新前の古い値が取得されてしまい、予期しない動作になる問題に遭遇しました。

Reactのstate更新は非同期であることは知っていましたが、実際の開発では思った以上に影響が大きく、かなり悩まされました。

発生した問題

ユーザーがボタンをクリックすると、カウントを増やした後に最新の値をAPIへ送信したいと考えていました。

const [count, setCount] = useState(0);

const handleClick = () => {
setCount(count + 1);

console.log(count);

sendToApi(count);
};

しかし、期待していた動作は

1
2
3
...

なのに、実際には

0
1
2
...

のように、1つ前の値が送信されてしまいます。

その結果、バックエンド側に誤ったデータが保存されることがありました。

試したこと

まず、以下のように変更してみました。

setCount(count + 1);

setTimeout(() => {
sendToApi(count);
}, 0);

しかし、これでも状況によっては古い値が送信されてしまい、根本的な解決にはなりませんでした。

現在の対応

一旦、以下のように新しい値を変数に保持してから利用しています。

const handleClick = () => {
const nextCount = count + 1;

setCount(nextCount);

sendToApi(nextCount);
};

また、以下のように useEffect を利用して処理する方法も検討しています。

useEffect(() => {
sendToApi(count);
}, [count]);
質問

Reactではstate更新直後に最新の値を利用したい場合、一般的にはどのような実装が推奨されるのでしょうか?

また、

一時変数を利用する方法
useEffect を利用する方法
useRef を利用する方法

などがありますが、実務ではどの方法が最もよく採用されているのでしょうか。

同じような問題を経験された方がいらっしゃいましたら、ベストプラクティスを教えていただけると嬉しいです。

0 likes

2Answer

非同期なので、いつ更新されるかわからないのが怖いところですね。
書かれている対処では、一見うまく行きますが、
クリックの連打をすると変な値になります。

prev を使って、前の値に+1するのが安全です。

const handleClick = () => {
  setCount(prev => prev + 1);
};

useEffect(() => {
  if (count === 0) return;
  sendToApi(count);
}, [count]);

ただし、この場合は連打したら途中が抜けます。(最終カウント数は正しい)

クリック全てに処理がほしいのなら、素直にref使うほうがいいかも

const countRef = useRef(0);
const handleClick = () => {
  countRef.current += 1;
  sendToApi(countRef.current);
};
1Like

Comments

  1. ご回答ありがとうございます。

    私自身、React の state 更新は非同期であることは理解していたつもりでしたが、実際の業務開発では「とりあえず期待した値が取れているように見えるコード」と「本当に安全に動作するコード」の違いを深く意識できていませんでした。そのため、今回ご指摘いただいた内容は非常に勉強になりました。

    特に私が例として挙げた

    const nextCount = count + 1;
    setCount(nextCount);
    sendToApi(nextCount);
    

    という実装は、一見すると問題なく動作しているように見えるため安心してしまっていました。しかし、よく考えてみると、ユーザーがボタンを素早く連打するような実際の運用環境では state の更新タイミングとのズレが発生する可能性があり、必ずしも安全とは言えないことに気付かされました。

    また、setCount(prev => prev + 1) を使用して前回の state を基準に更新するべきという点も大変参考になりました。React の更新キューやバッチ処理を考慮すると、確かにこちらの方がより堅牢な実装になりますね。

    さらに、useEffect を利用して state 更新後に API 送信を行う方法についても理解が深まりました。ただ、その場合は連続クリック時に途中の値が送信されず、最終的な state のみが反映されるケースがあるという点は、これまであまり意識していなかったため非常に興味深かったです。

    今回のケースでは「最新の状態を保存したい」のか、それとも「クリックされたすべてのイベントを漏れなく記録したい」のかによって、選択すべき実装方法が変わるということを改めて認識しました。もし後者のようにクリックごとの処理を確実に実行する必要があるのであれば、ご提案いただいた useRef を利用するアプローチの方が適しているのかもしれません。

    React を使い始めてから state と ref の使い分けについて何度か悩む場面がありましたが、今回のご説明を読んで「画面表示に影響する値は state、即時に参照・更新したい値やイベント管理用の値は ref」という考え方が少し整理できた気がします。

    とても分かりやすいご指摘と実践的なアドバイスをありがとうございました。実際のプロダクト開発で遭遇しそうなケースまで含めて考える良いきっかけになりました。

Your answer might help someone💌