Skip to content

Commit 8fe65f1

Browse files
committed
preact/signals: Make useComputed use the initial compute function forever
1 parent b5334c5 commit 8fe65f1

File tree

2 files changed

+59
-5
lines changed

2 files changed

+59
-5
lines changed

packages/preact/src/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -421,11 +421,12 @@ export function useSignal<T>(value?: T, options?: SignalOptions<T>) {
421421
)[0];
422422
}
423423

424-
export function useComputed<T>(compute: () => T, options?: SignalOptions<T>) {
425-
const $compute = useRef(compute);
426-
$compute.current = compute;
424+
export function useComputed<T>(
425+
compute: () => T,
426+
options?: SignalOptions<T>
427+
): ReadonlySignal<T> {
427428
(currentComponent as AugmentedComponent)._updateFlags |= HAS_COMPUTEDS;
428-
return useMemo(() => computed<T>(() => $compute.current(), options), []);
429+
return useState(() => computed(compute, options))[0];
429430
}
430431

431432
function safeRaf(callback: () => void) {

packages/preact/test/index.test.tsx

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ import {
1515
Component,
1616
} from "preact";
1717
import type { ComponentChildren, FunctionComponent, VNode } from "preact";
18-
import { useContext, useEffect, useRef, useState } from "preact/hooks";
18+
import {
19+
useContext,
20+
useEffect,
21+
useRef,
22+
useState,
23+
useCallback,
24+
} from "preact/hooks";
1925
import { setupRerender, act } from "preact/test-utils";
2026

2127
const sleep = (ms?: number) => new Promise(r => setTimeout(r, ms));
@@ -1001,4 +1007,51 @@ describe("@preact/signals", () => {
10011007
expect(spy).to.have.been.calledWith("willmount:1");
10021008
});
10031009
});
1010+
1011+
describe("useComputed", () => {
1012+
it("should keep using the compute initial compute function", async () => {
1013+
const s1 = signal(1);
1014+
const s2 = signal("a");
1015+
1016+
function App({ x }: { x: Signal }) {
1017+
const c = useComputed(() => {
1018+
return x.value;
1019+
});
1020+
return <span>{c.value}</span>;
1021+
}
1022+
1023+
render(<App x={s1} />, scratch);
1024+
expect(scratch.textContent).to.equal("1");
1025+
1026+
render(<App x={s2} />, scratch);
1027+
expect(scratch.textContent).to.equal("1");
1028+
1029+
s1.value = 2;
1030+
rerender();
1031+
expect(scratch.textContent).to.equal("2");
1032+
});
1033+
1034+
it("should not recompute when the compute function doesn't change and dependency values don't change", async () => {
1035+
const s1 = signal(1);
1036+
const spy = sinon.spy();
1037+
1038+
function App({ x }: { x: Signal }) {
1039+
const fn = useCallback(() => {
1040+
spy();
1041+
return x.value;
1042+
}, [x]);
1043+
1044+
const c = useComputed(fn);
1045+
return <span>{c.value}</span>;
1046+
}
1047+
1048+
render(<App x={s1} />, scratch);
1049+
expect(scratch.textContent).to.equal("1");
1050+
expect(spy).to.have.been.calledOnce;
1051+
1052+
rerender();
1053+
expect(scratch.textContent).to.equal("1");
1054+
expect(spy).to.have.been.calledOnce;
1055+
});
1056+
});
10041057
});

0 commit comments

Comments
 (0)