Skip to content

2.0.0-beta.15: isPending does not report pending for refresh() on a createOptimistic #2799

Description

@brenelz

Summary

With the optimistic primitives in @solidjs/signals, isPending reports inconsistent results. The same refresh() against an optimistic store does flip isPending. It's surprising that refresh() lights up isPending on one optimistic primitive but not the other.

Reproduction

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

import { createOptimistic, isPending, refresh, Loading } from 'solid-js';

type Result = { items: number[] };

function fetchItems(): Promise<Result> {
  return new Promise((resolve) =>
    setTimeout(() => resolve({ items: [1, 2, 3] }), 800)
  );
}

export default function Demo() {
  const [data] = createOptimistic(() => fetchItems());

  return (
    <Loading>
      {/* opacity never drops to 0.5 on refresh, even though the data refetches */}
      <div style={{ opacity: isPending(() => data().items) ? 0.5 : 1 }}>
        {JSON.stringify(data())}
        <button onClick={() => refresh(data)}>Refresh</button>
      </div>
    </Loading>
  );
}

Click Refresh: the data refetches, but isPending(() => data().items) remains false the entire time.

Swapping createOptimistic(() => fetchItems()) for createOptimisticStore(() => fetchItems(), { items: [] }) (and reading data.items directly) makes isPending correctly report true during the refresh.

Related: identity writes

A smaller, possibly-related inconsistency in the same area — an identity optimistic write behaves differently across the two primitives:

  • createOptimisticStore: setOpt(x => x) and setOpt(x => { x.items = x.items }) are both silent no-ops — the store dedupes structurally (n !== e in the store setter, and u === l in the property set trap), so the write machinery never runs.
  • createOptimistic: setOpt(x => x) still enters setSignal, which calls initTransition before the equality check, so the call enlists in the active transition.

This is arguably a natural consequence of store vs. atomic-signal semantics, but the fact that the same setOpt(x => x) idiom is inert for the store and observable for the computed is surprising for users moving between the two.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions