diff --git a/.changeset/fix-latest-loading-lane.md b/.changeset/fix-latest-loading-lane.md new file mode 100644 index 000000000..d6538097c --- /dev/null +++ b/.changeset/fix-latest-loading-lane.md @@ -0,0 +1,5 @@ +--- +"@solidjs/signals": patch +--- + +Keep `latest()` render readers independent from async Loading optimistic lanes so repeated refreshes are not held until unrelated async work settles. diff --git a/packages/signals/src/core/lanes.ts b/packages/signals/src/core/lanes.ts index 475d93a46..8c24c9f49 100644 --- a/packages/signals/src/core/lanes.ts +++ b/packages/signals/src/core/lanes.ts @@ -63,7 +63,6 @@ export function getOrCreateLane(signal: Signal): OptimisticLane { // the owner's write merges the companion's subscribers into this lane and // their effects wait on its async instead of flushing immediately. adoptCompanionLane(signal._x?._pendingSignal, lane); - adoptCompanionLane(signal._x?._latestValueComputed, lane); return lane; } diff --git a/packages/signals/tests/latest-async.test.ts b/packages/signals/tests/latest-async.test.ts index 33488c9b7..1d1250d98 100644 --- a/packages/signals/tests/latest-async.test.ts +++ b/packages/signals/tests/latest-async.test.ts @@ -19,6 +19,7 @@ */ import { createMemo, + createLoadingBoundary, createRenderEffect, createRoot, createSignal, @@ -51,6 +52,7 @@ it("latest(asyncMemo) settles to the resolved value and re-reports pending on ea await current.promise; return `AV${c}`; }); + createRenderEffect( () => latest(asyncValue), v => { @@ -99,3 +101,49 @@ it("latest(asyncMemo) settles to the resolved value and re-reports pending on ea expect(latestLog.at(-1)).toBe("AV2"); expect(pendingLog.at(-1)).toBe(false); }); + +it("latest render readers update while unrelated Loading work is pending (#3289)", async () => { + const [reload, setReload] = createSignal(0); + const gates = [deferred(), deferred(), deferred()]; + const tabValues: number[] = []; + + createRoot(() => { + const graphs = gates.map((gate, index) => + createMemo(async () => { + latest(reload); + await gate.promise; + return index; + }) + ); + + const boundary = createLoadingBoundary( + () => graphs.map(graph => [isPending(() => graph()), graph()]), + () => "loading" + ); + + createRenderEffect( + () => boundary(), + () => {} + ); + + createRenderEffect( + () => latest(reload), + value => { + tabValues.push(value); + } + ); + }); + + flush(); + expect(tabValues).toEqual([0]); + + for (let value = 1; value <= 3; value++) { + setReload(value); + flush(); + expect(tabValues.at(-1)).toBe(value); + } + + gates.forEach(gate => gate.resolve()); + await settle(); + expect(tabValues.at(-1)).toBe(3); +});