Repository navigation
Description
Describe the bug
A page creates an async memo that never resolves and is never read. The docs say "Only expressions that read an async value wait for it", and RULES-FUNDAMENTALS.md (Reader): "a pending value no render effect reaches holds nothing and reaches no boundary".
That holds on a plain mount, and under a held navigation when the inner <Show> has a literal when={true}. But when the page is mounted by a held update (the route memo is async) and the inner <Show>'s condition reads a memo, the unread pending memo holds the outer <Loading>: the navigation never lands and the screen stays on the inner fallback forever.
| shape | rc.13 | rc.20 | next@215c365 |
|---|---|---|---|
| not held | B | B | B |
held + when={true} |
B | B | B |
| held + memo condition | B | inner | inner |
Regression since rc.14. Related but distinct: #4203, #4340 (the latest()-mounted variants, fixed), #4479 (unread derived store).
Steps to Reproduce
import { render } from "@solidjs/web";
import { createMemo, createSignal, type JSX, Loading, Show } from "solid-js";
import { expect, it } from "vitest";
const wait = (ms: number) => new Promise((r) => setTimeout(r, ms));
type Shape = "held + memo condition" | "not held" | "held + when={true}";
async function navigate(shape: Shape): Promise<string> {
const [path, setPath] = createSignal("a");
const matched = createMemo(() => {
const p = path();
return p === "a" || shape === "not held" ? p : wait(10).then(() => p);
});
function Page() {
const local = createMemo(() => 1);
createMemo(() => (local(), new Promise<never>(() => {})));
return <p>B</p>;
}
function Boundary(props: { children: JSX.Element }) {
const cond = createMemo(() => undefined as boolean | undefined);
return (
<Loading fallback={<i>inner</i>}>
<Show when={shape === "held + when={true}" ? true : (cond() ?? true)}>{props.children}</Show>
</Loading>
);
}
const el = document.createElement("div");
const dispose = render(
() => (
<Loading fallback="root">
<Show when={matched() === "b"} fallback={<p>A</p>}>
<Boundary>
<Page />
</Boundary>
</Show>
</Loading>
),
el,
);
await wait(30);
expect(el.textContent).toBe("A");
setPath("b");
await wait(200);
const text = el.textContent ?? "";
dispose();
return text;
}
it.each<Shape>(["not held", "held + when={true}", "held + memo condition"])(
"%s: a pending memo nothing reads does not hold the page's <Loading>",
async (shape) => {
expect(await navigate(shape)).toBe("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
B in all three shapes: the never-resolving memo has no reader.
Actual behavior
held + memo condition: the text stays inner after 200ms (and forever).
Activity
test(web): import JSX from @solidjs/web in the #4542 spec
ryansolid commentedon Oct 31, 2026
Fixed on
nextby #4554 (62bf4c4); it ships in the next RC.Generated by Claude Code
fix(signals): a pending memo nothing reads holds no fresh Loading und…