2.0.0-rc.20: <Loading> stays on its fallback forever when an async memo's first answer is superseded while a sibling under the same boundary refetches
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 25/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- typescript
- 領域
- frontend
調査の方向性
Start by running the reproduction as a Vitest test with jsdom, and confirm that the control case passes while the sibling-refetch case shows <p>wait</p>. The bug sits in how a Loading boundary tracks pending async memos when a first promise is superseded by a synchronous recomputation, so the fix lives in the reactive core, not in the components shown. Done means both cases in the repro render <i>B</i><b>A</b> and the boundary recovers after later writes.
索引モデルが issue の本文から書いたものです。
説明
Describe the bug
Two async memos a and b are read under one <Loading>. b is on its first load. While it loads, a starts a refetch (returns a new promise). b's first answer then lands in the order data libraries use: the value is stored and the memo's source is bumped before the first promise's continuation reaches the core, so the first flight is superseded by a synchronous recomputation. a's refetch then settles.
Both memos now hold settled values, but <Loading> shows its fallback forever. Without the sibling refetch the same sequence renders the content.
- rc.13: renders
""(also wrong) - rc.20:
<p>wait</p>forever - next@215c365:
<p>wait</p>forever
Steps to Reproduce
Same setup as above. repro.test.tsx:
import { render } from "@solidjs/web";
import { createMemo, createSignal, Loading, Show } from "solid-js";
import { expect, it } from "vitest";
const wait = (ms: number) => new Promise((r) => setTimeout(r, ms));
async function run(refetchA: boolean): Promise<string> {
const [aVersion, setAVersion] = createSignal(0);
const [bVersion, setBVersion] = createSignal(0);
let aFlight: Promise<string> | null = null;
let releaseA: (v: string) => void = () => {};
let bValue: string | null = null;
let releaseB: (v: string) => void = () => {};
const bFlight = new Promise<string>((resolve) => (releaseB = resolve));
const el = document.createElement("div");
const dispose = render(() => {
const a = createMemo(() => (aVersion(), aFlight ?? "A"));
const b = createMemo(() => (bVersion(), bValue ?? bFlight.then((v) => v)));
return (
<Loading fallback={<p>wait</p>}>
<Show when={b()}>
<i>{b()}</i>
</Show>
<Show when={a()}>
<b>{a()}</b>
</Show>
</Loading>
);
}, el);
await wait(10);
expect(el.innerHTML).toBe("<p>wait</p>");
if (refetchA) {
aFlight = new Promise<string>((resolve) => (releaseA = resolve));
setAVersion(1);
}
await wait(0);
// b's first answer: the value is written and its version bumped before the
// first promise's continuation reaches the core (the order TanStack Query uses).
bValue = "B";
releaseB("B");
setBVersion(1);
await wait(20);
if (refetchA) {
aFlight = null;
releaseA("A");
setAVersion(2);
}
await wait(50);
const html = el.innerHTML;
dispose();
return html;
}
it("control: without a sibling refetch the content renders", async () => {
expect(await run(false)).toBe("<i>B</i><b>A</b>");
});
it("with a sibling refetch in the same window the content renders once both settle", async () => {
expect(await run(true)).toBe("<i>B</i><b>A</b>");
});
package.json
{
"name": "solid-repro",
"private": true,
"type": "module",
"scripts": { "test": "vitest run" },
"dependencies": {
"solid-js": "2.0.0-rc.20",
"@solidjs/web": "2.0.0-rc.20",
"@solidjs/signals": "2.0.0-rc.20"
},
"devDependencies": {
"@solidjs/vite-plugin": "3.0.0-next.54",
"jsdom": "30.1.2",
"vitest": "5.0.5"
}
}
vitest.config.ts
import solid from "@solidjs/vite-plugin";
import { defineConfig } from "vitest/config";
export default defineConfig({
plugins: [solid()],
test: { environment: "jsdom" },
});
Expected behavior
<i>B</i><b>A</b> in both cases.
Actual behavior
Control passes; with the sibling refetch the boundary stays at <p>wait</p> and never recovers (no further writes revive it).
Platform
As above.
- 主要言語
- TypeScript
- スター
- 36.2k
- フォーク
- 1.1k
- 平均マージ
- 11時間 5分
- マージ済み PR(30日)
- 475
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
solidjs/solid のほかの issue
-
難易度 4/5 3〜5日 初心者へのやさしさ 28/100
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 12/100
メンテナーはふだん 1 日以内に返信
-
[2.0 / next] <Errored> recovery shows a pending memo's answer to an older input beside the new inputオープン
難易度 4/5 3〜5日 初心者へのやさしさ 20/100
メンテナーはふだん 1 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 15/100
メンテナーはふだん 1 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 18/100
メンテナーはふだん 1 日以内に返信
似ている issue
-
🐛 bug
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
LibreChat-AI/LibreChat#17733 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
TypeStrong/typedoc#3150 ·
-
accessibility bug good first issue
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
メンテナーはふだん 1 日以内に返信
-
Normalize image-source MIME types before SVG dispatch対応中かも @HarsH-1877 が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
tscircuit/image-utils#58 · コメント 1 件 ·
-
難易度 1/5 1時間未満 初心者へのやさしさ 74/100
メンテナーはふだん 2 日以内に返信