コース一覧
フロントデバッグ道場 呪われたウィジェット供養の10問
検索結果が入れ替わる

フロントデバッグ道場 呪われたウィジェット供養の10問

React で書かれた壊れたウィジェットを、自分の手で直していく演習専用コースです。解説はありません。症状と完成条件だけを読み、原因を突き止めて直します。手が止まったときのために、方針・調べ方・部分解の3段階のヒントを各問に用意しています。他の道場と違い、白紙からではなく「動いているように見えて壊れているコード」から始まります。関数名のタイポ、フォームの既定動作、key 忘れ、プロパティ名の取り違えといった見つけやすい呪いから始まり、state の直接変更、useEffect の無限ループ、set 直後の古い値、非同期応答の追い越しといった再現しにくい呪いへ進み、最後は5つの呪いが同時にかかったウィジェットを1つずつ払います。1問15分から45分、全10問で約4.5時間です。React入門を終えて「エラーが出ると手が止まる」と感じている方に向いています。

1
初段
01. ボタンが反応しない15分
02. 送信すると消える15分
03. 黄色い警告を消せ20分
04. undefined が出る20分
2
中段
01. 数字が増えない25分
02. 無限ループを止めろ25分
03. 一つ前の値が表示される30分
04. 検索結果が入れ替わる30分
3
師範
01. 赤いエラーを読んで直せ35分
02. 総合 5つの呪いを全部払え45分

検索結果が入れ替わる

中段 / 目安 30分

琥珀さんが珍しく困った顔をしています。「たまーに、検索結果が変なんだ。『あ』で検索したのに、後から前の検索の結果に上書きされる。再現しにくいのが一番怖い」。

今回のお店さがしウィジェットは、種類を選ぶたびにサーバーへ問い合わせて一覧を出します。ふだんは正しく動きます。ところが回線が細い日には、選んだのと違う種類の一覧が最後に居座ることがあります。

原因は 非同期の応答は、送った順に返ってくるとは限らない ことです。

食べものを選んで問い合わせ、返事を待っている間に本を選ぶと、問い合わせが2本、同時に空を飛んでいる状態になります。ここで食べもののほうが混雑して遅れると、次のような順で戻ってきます。

  1. 本の応答が先に届く。画面に本のお店が並ぶ (ここまでは正しい)
  2. 遅れていた食べものの応答が届く。画面が食べものに書きかわる

利用者から見れば「本を選んだのに食べものが出た」です。どちらの応答も正しく、通信もエラーになっていません。古い問い合わせの答えが、新しい答えを上書きしただけです。これが競合 (race condition) と呼ばれる呪いです。

この呪いが怖いのは、速い回線では起きないことです。開発中の自分の手元では一度も再現せず、お客さんの環境でだけ起きます。今回の道場では mock-api.json の delaySteps で 1回目は速く、2回目をわざと遅く してあります。つまり誰の手元でも同じように壊れます。呪いを直したかどうかが、はっきり目に見えます。

直し方の考え方は「その応答が、まだ最新の問いに対するものか」を判定することです。React が用意している道具は useEffect の クリーンアップ関数です。効果が次に走る直前、React は前回のクリーンアップを必ず呼びます。ここで「この回はもう古い」という印を立てておけば、あとから届いた応答は自分が古いことを知って、静かに引き下がれます。

JavaScript (JSX)

useEffect(() => { let ignore = false; // 通信して、届いたら ignore を見てから state を更新する return () => { ignore = true; }; }, [genre]);

ignore は効果が走るたびに新しく作られる変数で、その回の通信だけが見ています。だから「この回はもう古い」を回ごとに独立して記録できます。

印を見る場所も大事です。state を更新する直前に見てください。手前で見てしまうと、通信を待っている間に古くなった回を止められません。

完成条件

  • 種類を続けて2つ選ぶと、最後に選んだ種類の一覧が画面に残る
  • 遅れて届いた古い応答で、画面が書きかわらない
  • 受信回数 (className が status の段落) は今までどおり、届いたぶんだけ数える
  • useEffect からクリーンアップ関数を返し、その回が古くなったことを記録する

進め方

まず壊れているところを自分の目で見てください。食べものを選び、すぐ続けて本を選びます。1秒半ほど待つと、画面が食べものに戻るはずです。この「戻る」が呪いです。

次に、なぜ戻るのかを順番で説明できるようにします。2本の通信が同時に飛んでいて、遅いほうが後に着く。ここまで言えたら、あとは着いたほうに「もう遅い」と伝える手段を用意するだけです。

受信回数はあえて残してあります。クリーンアップを書いても通信そのものは止まらないので、受信回数は2回とも増えます。増えるけれど画面は書きかわらない、という状態が正解です。ここを取り違えて通信自体を止めようとすると、遠回りになります。

ヒントは3段階で、①方針、②使う構文、③部分解の順に出ます。1つも開けずに通せたら、その問題は自分のものです。

供養が済むと、琥珀さんが笑って言います。「"たまに壊れる"を直せる人は、どこでも生きていけるよ」。

ヒント

前のヒントを開くと次が開きます。開かずに解けると未開封クリアです

①方針 通信が2本同時に飛んでいて、遅いほうが後に着くのが症状の正体です。着いた応答に「これはまだ最新の問いへの答えか」を判定させてください。判定に使う印は、効果が走るたびに新しく作られる必要があります。

ヒント 2ヒント 1 を開くと読めます
ヒント 3ヒント 2 を開くと読めます
App.jsx
mock-api.json
styles.css
プレビュー

できているか

  • はじめは5件すべて出ている
  • 食べもの・本と続けて選ぶと、遅れて届いた食べものに戻らない
  • クリーンアップ関数を返している
エディタを読み込んでいます