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

Cerrado
#4,541 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
25/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
typescript
Área
frontend

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.

Lenguaje dominante
TypeScript
Estrellas
36.2k
Forks
1.1k
Merge medio
10 h 10 min
PR fusionados (30 d)
505

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de solidjs/solid

Todos los issues de solidjs/solid

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.