Skip to content

latest() returns the committed value instead of the in-flight value on first use during a held transition #3041

Closed

Description

@tehsausage

Describe the bug

Hi I am trying to learn SolidJS for the first time with 2.0, and I ran in to what seems to either be unintuitive / inconsistent behavior of latest() or a bug.

Your Example Website or App

https://stackblitz.com/edit/solidjs-templates-93va2vbn?file=src%2FApp.tsx

Steps to Reproduce the Bug or Issue

import { createEffect,  createSignal,  createMemo,  Loading,  isPending,  latest } from 'solid-js';

// Self-resolving fake fetch so the demo needs no network.
const fakeFetch = (name: string) =>
  new Promise<{ name: string }>((r) => setTimeout(() => r({ name }), 1000));

export default function App() {
  const [query, setQuery] = createSignal('pikachu');
  const pokemon = createMemo(() => fakeFetch(query()));
  const pending = () => isPending(() => pokemon());

  // Un-commenting this works around the issue
  /*
  createEffect(
    () => latest(() => query()),
    () => {}
  ); // warm the companion at mount
  */

  return (
    <div>
      <button onClick={() => setQuery('pikachu')}>pikachu</button>
      <button onClick={() => setQuery('charizard')}>charizard</button>
      <button onClick={() => setQuery('gengar')}>gengar</button>

      <Loading fallback={<div>skeleton</div>} on={query}>
        <div>Current: {pokemon().name}</div>
        {/* latest(query) is NOT evaluated during initial load: the
            short-circuit means it is first called DURING the first transition. */}
        {pending() && (
          <p>
            Now Loading: <b>{latest(() => query())}</b>
          </p>
        )}
      </Loading>
    </div>
  );
}

Clicking between the buttons seems to, somewhat non-deterministically, display the previous pokemon's name instead of the upcoming pokemon's name.

Expected behavior

I would expect latest(() => query()) to always show the latest value -- and it does, if the shown work-around is commented out.

Screenshots or Videos

No response

Platform

Linux, Firefox

Additional context

LLM indicated the issue as being this:

In @solidjs/signals (dist/dev.js of @solidjs/[email protected]):

  • latest() → latestRead(el) reads a per-source latest-value companion
    (el._latestValueComputed), created lazily by getLatestValueComputed on first use:

    // dev.js:4096
    function getLatestValueComputed(el) {
      if (!el._latestValueComputed) {
        ...
        el._latestValueComputed = optimisticComputed(() => read(el));
        ...
      }
      return el._latestValueComputed;
    }

    Note: no backfill. The companion's compute read(el) is first executed (or forced
    by latestRead's prepareComputed) after the pending write was already processed.
    Inside the held transition's render context (stale = true for render effects, set at
    dev.js:2587-2589), read(el) returns the committed value.

  • When a write is processed during a transition, the companion is updated only if it
    already exists
    (syncCompanions, dev.js:4030):

    function syncCompanions(el, value) {
      if (el._pendingSignal) updatePendingSignal(el);
      if (el._latestValueComputed) setSignal(el._latestValueComputed, value);
    }

    The app's banner is gated, so the companion does not exist at write time on the
    first transition; it is created afterwards with the committed value and never learns
    the in-flight write.

  • Compare getPendingSignal (dev.js:3909), which does backfill:

    function getPendingSignal(el) {
      if (!el._pendingSignal) {
        el._pendingSignal = optimisticSignal(false, { ownedWrite: true });
        el._pendingSignal._parentSource = el;
        if (computePendingState(el)) setSignal(el._pendingSignal, true); // backfill
      }
      return el._pendingSignal;
    }

    This is why isPending() is correct on first use during a transition while
    latest() is not.

Suggested framework fix

In getLatestValueComputed, backfill the current in-flight value when the companion is
created, mirroring getPendingSignal — e.g., if the source currently carries a pending
write (el._pendingValue !== NOT_PENDING or the equivalent optimistic-lane read),
initialize the companion to that value rather than only to read(el).

Activity

  1. brenelz commented on Aug 23, 2026

    @brenelz
    Contributor

    Yeah this seems like a bug. If i remove the pending check it works as expected.

  2. ryansolid commented on Aug 24, 2026

    @ryansolid
    Member

    Fixed on next by 3585866 (port of #3042, credit @brenelz): the latest-value companion backfills the in-flight write on lazy creation, so the first latest() read during a held transition sees the pending value, matching isPending(). Ships in the next 2.0 release.

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