Repository navigation
Description
Describe the bug
A list page reads an async memo, and the list fetch fails, so the <Errored> around the page shows its fallback. The user then navigates to another page, which is a different branch under the same <Errored>. If the list page read the failed memo in a <Show when> or a <Match when>, for example the usual empty-state check <Show when={rows().length === 0}>, the boundary keeps showing its fallback after the navigation, and the app stays on the error until a reload.
A plain read of the same memo (text, <For>, <Repeat>, an attribute, latest()) recovers on navigation. A wider sweep found the same failure for a memo of the condition used in a <Show>, for a keyed <Show>, and with or without a <Loading> around the page, and with the navigation done by <Show> or by <Switch>. Recovery by refresh() or by changing the memo's input works in every case.
Found by fuzzing reactivescript's example apps (a notes app whose list uses an empty-state <Show>), then reduced to plain Solid.
Your Example Website or App
The vitest spec below (in packages/web/test/), on next @ 4235019.
Steps to Reproduce the Bug or Issue
import { render } from "@solidjs/web";
import { createMemo, createSignal, Errored, flush, For, Loading, Match, Show, Switch } from "solid-js";
import { expect, it } from "vitest";
const tick = async () => { for (let i = 0; i < 2; i++) { await new Promise(r => setTimeout(r, 0)); flush(); } };
type Row = { id: number };
const readers: Record<string, (rows: () => Row[]) => any> = {
"Show when={rows().length === 0}": rows => <Show when={rows().length === 0} fallback={<p>{rows().length} rows</p>}><p>no rows</p></Show>,
"Show when={rows()}": rows => <Show when={rows()}>{r => <p>{r().length} rows</p>}</Show>,
"Switch/Match": rows => <Switch fallback={<p>{rows().length} rows</p>}><Match when={rows().length === 0}><p>no rows</p></Match></Switch>,
"text (control)": rows => <p>{rows().length} rows</p>,
"For (control)": rows => <ul><For each={rows()}>{r => <li>{r.id}</li>}</For></ul>,
};
for (const [name, view] of Object.entries(readers))
it(`${name}: navigating away from a failed list clears <Errored>`, async () => {
let rejectList!: (e: Error) => void;
const el = document.createElement("div");
let setPage!: (p: string) => void;
const dispose = render(() => {
const [page, set] = createSignal("list"); setPage = set;
const rows = createMemo(() => new Promise<Row[]>((_, reject) => { rejectList = reject; }));
return (
<Errored fallback={<p>could not load</p>}>
<Show when={page() === "list"} fallback={<p>about page</p>}>
<Loading fallback={<p>loading</p>}>{view(rows)}</Loading>
</Show>
</Errored>
);
}, el);
await tick();
rejectList(new Error("boom")); await tick();
const failed = el.textContent;
setPage("about"); flush(); await tick();
const after = el.textContent;
dispose();
expect({ failed, after }).toEqual({ failed: "could not load", after: "about page" });
});npx vitest run in packages/web. The two control tests pass; the Show and Switch/Match tests fail with after: "could not load".
Expected behavior
After navigating away, nothing under the boundary reads the failed memo, so <Errored> renders the new branch (about page), as it does when the list page reads the memo as text or in a <For>.
Platform
- OS: macOS
- Browser: n/a (jsdom)
- Version: solid-js 2.0.0-rc.20,
next@ 4235019
Activity
test(web): import JSX from @solidjs/web in the #4644 spec
fix(signals): only readers the Errored caught keep its fallback up
fix(signals): walk each upstream node once in the Errored heal check (#…
refactor(signals): shorter heal-check walk (#4644)
fix(signals): an Errored heals when nothing reads the failed source a…
ryansolid commentedon Nov 3, 2026
Fixed on
nextby #4653 (dd5f9d1); it ships in the next RC.Generated by Claude Code