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.
Summary
With the optimistic primitives in
@solidjs/signals,isPendingreports inconsistent results. The samerefresh()against an optimistic store does flipisPending. It's surprising thatrefresh()lights upisPendingon one optimistic primitive but not the other.Reproduction
https://stackblitz.com/edit/solidjs-templates-bhrlafjc?file=src%2FApp.tsx
Click Refresh: the data refetches, but
isPending(() => data().items)remainsfalsethe entire time.Swapping
createOptimistic(() => fetchItems())forcreateOptimisticStore(() => fetchItems(), { items: [] })(and readingdata.itemsdirectly) makesisPendingcorrectly reporttrueduring 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)andsetOpt(x => { x.items = x.items })are both silent no-ops — the store dedupes structurally (n !== ein the store setter, andu === lin the propertysettrap), so the write machinery never runs.createOptimistic:setOpt(x => x)still enterssetSignal, which callsinitTransitionbefore 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.