Skip to content

[2.0 rc.19 / next] A reader of a derived store that uses latest() sees the store's seed for one pass after its async source resolves #4355

Closed

Description

@ibniss

Describe the bug

A derived store whose derive reads an async memo through latest() resolves normally, but the first computation that reads it afterwards sees the store's seed instead of what the derive just wrote. A render effect downstream commits undefined for one pass before the real value arrives.

import { createMemo, createRenderEffect, createRoot, createStore, flush, latest } from "solid-js";

createRoot(() => {
  const record = createMemo(async () => {
    await new Promise(r => setTimeout(r, 20));
    return { name: "Hartford" };
  });
  const [state] = createStore(d => {
    d.value = { name: latest(() => record().name) };
  }, {});
  const view = createMemo(() => state.value?.name);
  createRenderEffect(view, v => {
    console.log("effect saw", v);
  });
});
flush();

Output:

effect saw undefined
effect saw Hartford

With logging added to each computation, the order is:

  3ms state derive threw NotReadyError
 22ms state derive ok
 23ms view, state.value = undefined     ← the derive above has already written value
 25ms effect saw undefined
 25ms view, state.value = {"name":"Hartford"}
 26ms effect saw Hartford

Your Example Website or App

Inline above: solid-js only.

Steps to Reproduce the Bug or Issue

  1. Save the snippet as repro.mjs next to an install of solid-js@2.0.0-rc.19.
  2. Run node --conditions=browser --conditions=development repro.mjs.
  3. See effect saw undefined before effect saw Hartford.

Expected behavior

The reader either stays pending until the store has resolved, or sees { name: "Hartford" } straight away. The effect only ever commits "Hartford".

Platform

  • OS: Linux
  • Runtime: Node 24.21.0 (no browser involved)
  • Versions: solid-js 2.0.0-rc.19, @solidjs/signals 2.0.0-rc.19. Also reproduces with @solidjs/signals built from next at 56a982c, and was already present on rc.15 and rc.16.

Additional context

What matters.

  • latest() in the derive is needed. With d.value = { name: record().name }, the reader sees { name: "Hartford" } on its first run and the effect never sees undefined.
  • The reader's kind does not matter: a memo (as above) and a second derived store that copies state.value behave the same.
  • The reader's first run is the stale one. It does not run while the source is pending; it first runs right after the store's derive succeeds, and reads the seed.

What we think happens. We have not traced the cause. Our guess is that with latest() the store's write lands outside whatever transition or pending lane the reader is scheduled in, so the reader's first read is served from the committed seed rather than the value just written. We may be misreading how latest() is meant to interact with a derive.

Where we hit it. A form-draft helper keeps a derived "state" store over a live record (read through latest()), plus a second derived store for display. After a client navigation, the display store exposed every field as undefined for one render; a form component called name.trim() and threw. Our workaround: until the state store reports itself initialized, the display store copies latest(source) directly, instead of reading the state store.

Activity

  1. ryansolid commented on Oct 28, 2026

    @ryansolid
    Member

    Fixed on next by #4369 (17ec571); 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