Skip to content

[2.0 / next] createOptimisticStore: a guess on an errored store brings back the previous question's answer when the re-ask lands #4526

Closed

Description

@ryansolid

On next @ ecf3011. Found by the ReactiveScript timing fuzzer (issue tracker app, Solid arm), reduced to plain Solid.

A list store is asked by a filter. The filter changes and the new question fails, so the Errored boundary shows the error. An action then guesses on the store (removes a row). That re-asks the question; when the answer ([#2]) lands, the screen shows the previous question's rows ([#1][#3][#4]), without the guess applied, under the new filter. It stays wrong until the action's own refresh lands.

/**
 * @jsxImportSource @solidjs/web
 * @vitest-environment jsdom
 */
import { test } from "vitest";
import { For, Loading, Errored, action, createOptimisticStore, createSignal, flush, refresh } from "solid-js";
import { render } from "../src/index.js";
const tick = async () => { for (let i = 0; i < 4; i++) { await new Promise(r => setTimeout(r, 0)); flush(); } };
test("guess on an errored store brings back the previous question's answer", async () => {
  const asks: { key: string; res: (v: any) => void; rej: (e: any) => void }[] = [];
  const ask = (key: string) => new Promise<any>((res, rej) => asks.push({ key, res, rej }));
  const settle = async (key: string, f: (a: any) => void) => { const i = asks.findIndex(a => a.key === key); f(asks.splice(i, 1)[0]); await tick(); };
  const container = document.body.appendChild(document.createElement("div"));
  const frames: string[] = []; const snap = (l: string) => frames.push(`${l}: ${container.textContent}  air ${asks.map(a => a.key)}`);
  let setFilter!: (f: string) => void; let closeOne!: (id: number) => any;
  render(() => {
    const [filter, sf] = createSignal("open"); setFilter = sf;
    const [issues, set] = createOptimisticStore<any[]>(() => ask(`issues:${filter()}`), [], { key: "id" });
    closeOne = action(function* (id: number) {
      set(list => { const i = list.findIndex(t => t.id === id); if (i >= 0) list.splice(i, 1); });
      yield ask(`close:${id}`);
      refresh(issues);
    });
    return <Loading fallback={<p>loading</p>}><Errored fallback={e => <p>error {e().message}</p>}><p>{filter()}: <For each={issues}>{(it: any) => <i>[#{it.id}]</i>}</For></p></Errored></Loading>;
  }, container);
  await tick();
  await settle("issues:open", a => a.res([{ id: 1 }, { id: 3 }, { id: 4 }])); snap("loaded");
  setFilter("closed"); await tick(); snap("filter closed");
  await settle("issues:closed", a => a.rej(new Error("declined"))); snap("closed failed");
  closeOne(1); await tick(); snap("close #1");
  await settle("issues:closed", a => a.res([{ id: 2 }])); snap("closed answered");
  await settle("close:1", a => a.res({})); snap("close accepted");
  await settle("issues:closed", a => a.res([{ id: 1 }, { id: 2 }])); snap("refresh answered");
  console.log(frames.join("\n"));
});

Actual:

loaded: open: [#1][#3][#4]
filter closed: open: [#1][#3][#4]
closed failed: error declined
close #1: error declined                air close:1,issues:closed
closed answered: closed: [#1][#3][#4]   <- the open answer, no guess, labelled "closed"
close accepted: closed: [#1][#3][#4]
refresh answered: closed: [#1][#2]

Expected at closed answered: the closed answer with the guess on it (closed: [#2]), or a hold on the error until the action ends. The open answer's rows should not come back once the closed question has been asked. Same frames with <Loading on={filter()}>.

Activity

  1. ryansolid commented on Oct 31, 2026

    @ryansolid
    MemberAuthor

    Fixed on next by #4532 (3a73a36); it ships in the next RC.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions