Skip to content

Commit 5db1295

Browse files
authored
preact/signals: Recompute useComputed on rerender (#754)
1 parent b5334c5 commit 5db1295

File tree

3 files changed

+76
-5
lines changed

3 files changed

+76
-5
lines changed

.changeset/perfect-dolls-clean.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@preact/signals": patch
3+
---
4+
5+
Update useComputed compute function on rerender

packages/preact/src/index.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -421,11 +421,18 @@ 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> {
428+
const [$fn, $computed] = useMemo(() => {
429+
const $fn = signal(compute);
430+
return [$fn, computed(() => $fn.value(), options)] as const;
431+
}, []);
432+
427433
(currentComponent as AugmentedComponent)._updateFlags |= HAS_COMPUTEDS;
428-
return useMemo(() => computed<T>(() => $compute.current(), options), []);
434+
$fn.value = compute;
435+
return $computed;
429436
}
430437

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

packages/preact/test/index.test.tsx

Lines changed: 60 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,57 @@ describe("@preact/signals", () => {
10011007
expect(spy).to.have.been.calledWith("willmount:1");
10021008
});
10031009
});
1010+
1011+
describe("useComputed", () => {
1012+
it("should recompute and update dependency list when the compute function changes", async () => {
1013+
const s1 = signal(1);
1014+
const s2 = signal("a");
1015+
1016+
function App({ x }: { x: Signal }) {
1017+
const fn = useCallback(() => {
1018+
return x.value;
1019+
}, [x]);
1020+
1021+
const c = useComputed(fn);
1022+
return <span>{c.value}</span>;
1023+
}
1024+
1025+
render(<App x={s1} />, scratch);
1026+
expect(scratch.textContent).to.equal("1");
1027+
1028+
render(<App x={s2} />, scratch);
1029+
expect(scratch.textContent).to.equal("a");
1030+
1031+
s1.value = 2;
1032+
rerender();
1033+
expect(scratch.textContent).to.equal("a");
1034+
1035+
s2.value = "b";
1036+
rerender();
1037+
expect(scratch.textContent).to.equal("b");
1038+
});
1039+
1040+
it("should not recompute when the compute function doesn't change and dependency values don't change", async () => {
1041+
const s1 = signal(1);
1042+
const spy = sinon.spy();
1043+
1044+
function App({ x }: { x: Signal }) {
1045+
const fn = useCallback(() => {
1046+
spy();
1047+
return x.value;
1048+
}, [x]);
1049+
1050+
const c = useComputed(fn);
1051+
return <span>{c.value}</span>;
1052+
}
1053+
1054+
render(<App x={s1} />, scratch);
1055+
expect(scratch.textContent).to.equal("1");
1056+
expect(spy).to.have.been.calledOnce;
1057+
1058+
rerender();
1059+
expect(scratch.textContent).to.equal("1");
1060+
expect(spy).to.have.been.calledOnce;
1061+
});
1062+
});
10041063
});

0 commit comments

Comments
 (0)