1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

localStorage.clear()でReactの表示が古いままになる:実ブラウザで修正前後を検証する

1
Posted at

同じサイトを2タブで開き、片方で localStorage.clear() を実行したとき、もう片方のReactの表示も空になるでしょうか。

use-local-storage-state の固定した2つのソースで確認しました。元版では、実際の保存値が消え、ブラウザの storage イベントも届いているのに、2つのフックの表示が古いまま残りました。既存の修正PRでは、同じテストが再読み込み前に成功します。

持ち帰れるものは、ブラウザが生成したイベントを使う3本のPlaywrightテストです。別のテスト用CLIやAPIキーは使いません。

同じ3テストで比較した結果

2026-10-20、Chromium、React 18.3.1、1 worker、retry 0、skip 0で各版を1回ずつ実行しました。

テスト 元版 799291ab 修正PR f53a57a5
別タブの setItem で完全なレコードが更新され、もう1キーが残る PASS PASS
同じタブ内のiframeが sessionStorage.clear() をしてもlocalStorageの値と表示が残る PASS PASS
別タブの localStorage.clear() 後、再読み込み前に両方の表示が空になる FAIL PASS

実行時間はテスト全体で元版5.734秒、修正PR3.673秒でした。速度比較を目的とした反復測定ではありません。

修正と元のjsdomテストは feritcemkahramanさんのPR #86 によるものです。今回追加したのは、実ブラウザのテスト、Reactの利用側fixture、測定記録です。測定時点ではPRは未マージでした。

見落としやすいのは key: null

HTML Standardの clear() の手順 は、保存値を消去した後、キー・変更前の値・変更後の値を null として通知するよう定めています。MDNの storage イベントの説明 にある通り、localStorage と sessionStorage ではイベントを共有する範囲も異なります。

今回の実ブラウザでは、別タブのボタンから本物の localStorage.clear() を実行し、読み取り側で次を確認しました。

{
  key: null,
  oldValue: null,
  newValue: null,
  isTrusted: true,
  area: 'local'
}

area はfixtureが event.storageArea と localStorage / sessionStorage を照合して記録する独自の項目です。isTrusted を手動で作ったり、dispatchEvent() で通知を代用したりはしていません。

元版では、保存値を getItem すると2キーとも null。しかし、画面には合成データのタスクと設定が残りました。

元版:消去後も2つのフックの表示が残る

修正PRでは、同じ消去操作の後、再読み込み前に表示も空になります。

修正PR:同じ消去後に両方の表示が空になる

消去操作自体は意図通りに動いています。検出している問題は、消去後の保存値とReactの表示の不一致です。

再読み込みで直っても、途中の失敗を消さない

元版も、再読み込みすれば空の表示に戻ります。最後の表示だけを確認すると、途中で古い値を見せていたことが分からなくなります。

そこで、実テストでは再読み込み前の正常系アサーションを残し、その成否を再読み込み後まで保持します。以下は 公開したspec の中心部分の抜粋です。env は2つの実ページ、empty は両フックが空の期待値です。

let staleAssertion;
try {
  await expect(env.reader.getByTestId('hook-state'))
    .toHaveText(JSON.stringify(empty), { timeout: 2000 });
} catch (error) {
  staleAssertion = error;
}

// この間に保存値・表示・実イベントを記録し、画像も保存する
await env.reader.reload();
await expect(env.reader.getByTestId('hook-state'))
  .toHaveText(JSON.stringify(empty));

if (staleAssertion) throw staleAssertion;

元版は2本の対照テストがPASSし、この正常系アサーションがFAILして終了コード1になります。修正PRは同じアサーションで3本ともPASSします。元版の失敗を隠すための test.fail() は使っていません。

自分で動かす

テスト一式のZIP を別の検証フォルダへ展開してください。Node 22以上を使います。ZIPには上流ソースや node_modules は含めていません。

npm ci --ignore-scripts --no-audit --no-fund
npx playwright install chromium

READMEの手順 で固定ソースを取得した後、次の2コマンドを別々に実行します。最初の終了コード1は、元版の不一致を検出した結果です。

STORAGE_CLEAR_SOURCE_DIR=./upstream-base npm test
STORAGE_CLEAR_SOURCE_DIR=./upstream-pr npm test

上に示したテスト時間には、ブラウザ本体の初回ダウンロード時間は含めていません。他の端末での成功や導入時間までは確認していません。

既存のPlaywright環境へ取り込む場合は、specと consumer.mjs を一緒に置きます。React / ReactDOM / esbuildが必要です。ほかのフックに適用するときは、フックのimport、ソース確認用のハッシュ、キー、利用側の操作、完全な期待レコードを対象に合わせます。

この結果で確認した範囲

確認したのはローカルChromium、2キー、defaultValue なしの組み合わせです。sessionStorage側の対照テストは、localStorageの値と表示が残ることを確認しています。内部のコールバックが一切発火しないことまでは計測していません。

既存のjsdomテストに、ブラウザ生成のイベントと実際の2タブ・iframeの条件を追加する検証です。Firefox、WebKit、SSR、容量超過、すべてのデフォルト値やserializer、上流の全テストを網羅した結果ではありません。

ソースを変更せずに比較し、各版の全17 Git blobも実行前後で照合しました。テスト本体が照合するのはフックのハッシュです。全ソースの照合は今回の測定作業で別途行っています。

測定記録とソース一式 に、チェックポイント、固定したバージョン、未加工の画像を残しています。同じリポジトリには、保存・復元の境界を確認するテストも公開しています。

検証と執筆はAI支援で行い、実行記録とソースに照合しました。上流作者による本テストの実行や、製品全体の信頼性を確認したという主張は含みません。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?