Skip to content

Commit d31738f

Browse files
authored
Revert computed changes in react/preact (#777)
* Revert "Add note about useComputed (#775)" This reverts commit f32e357. * Revert "preact/signals: Recompute useComputed on rerender (#754)" This reverts commit 5db1295. * Add changeset
1 parent 1ac2b12 commit d31738f

File tree

5 files changed

+11
-128
lines changed

5 files changed

+11
-128
lines changed

.changeset/healthy-lamps-teach.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@preact/signals": minor
3+
"@preact/signals-react": minor
4+
---
5+
6+
Revert the changes to `useComputed`, sincere apologies for the inconvenience we've discussed this at length and are going to side on the perf side.

packages/preact/README.md

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -72,35 +72,6 @@ function Counter() {
7272
}
7373
```
7474

75-
> **Optimizing `useComputed` performance**
76-
>
77-
> The `useComputed` hook follows the convention of keeping closures fresh. By default, the callback function passed to `useComputed` will re-run on every component render to ensure it always has access to the latest values from the component's scope. However, if the computed value doesn't change, updates won't propagate to dependent nodes in the signal graph.
78-
>
79-
> For expensive computations, you can optimize performance by memoizing the callback with `useCallback`:
80-
>
81-
> ```js
82-
> import { useSignal, useComputed } from "@preact/signals";
83-
> import { useCallback } from "preact/hooks";
84-
>
85-
> function Counter() {
86-
> const count = useSignal(0);
87-
>
88-
> // Memoize the callback to avoid re-running expensive calculations
89-
> const expensiveComputation = useCallback(() => {
90-
> let result = count.value;
91-
> for (let i = 0; i < 10000000; i++) {
92-
> result += 1;
93-
> }
94-
> return result;
95-
> // Empty deps means callback never changes, alternatively add count here so that if the identity of the signal changes this re-runs
96-
> }, []);
97-
>
98-
> const computed = useComputed(expensiveComputation);
99-
>
100-
> return <div>Result: {computed}</div>;
101-
> }
102-
> ```
103-
10475
### Rendering optimizations
10576

10677
The Preact adapter ships with several optimizations it can apply out of the box to skip virtual-dom rendering entirely. If you pass a signal directly into JSX, it will bind directly to the DOM `Text` node that is created and update that whenever the signal changes.

packages/preact/src/index.ts

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

426-
export function useComputed<T>(
427-
compute: () => T,
428-
options?: SignalOptions<T>
429-
): ReadonlySignal<T> {
430-
const [$fn, $computed] = useMemo(() => {
431-
const $fn = signal(compute);
432-
return [$fn, computed(() => $fn.value(), options)] as const;
433-
}, []);
434-
426+
export function useComputed<T>(compute: () => T, options?: SignalOptions<T>) {
427+
const $compute = useRef(compute);
428+
$compute.current = compute;
435429
(currentComponent as AugmentedComponent)._updateFlags |= HAS_COMPUTEDS;
436-
$fn.value = compute;
437-
return $computed;
430+
return useMemo(() => computed<T>(() => $compute.current(), options), []);
438431
}
439432

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

packages/preact/test/index.test.tsx

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

2721
const sleep = (ms?: number) => new Promise(r => setTimeout(r, ms));
@@ -1076,57 +1070,4 @@ describe("@preact/signals", () => {
10761070
expect(spy).to.have.been.calledWith("willmount:1");
10771071
});
10781072
});
1079-
1080-
describe("useComputed", () => {
1081-
it("should recompute and update dependency list when the compute function changes", async () => {
1082-
const s1 = signal(1);
1083-
const s2 = signal("a");
1084-
1085-
function App({ x }: { x: Signal }) {
1086-
const fn = useCallback(() => {
1087-
return x.value;
1088-
}, [x]);
1089-
1090-
const c = useComputed(fn);
1091-
return <span>{c.value}</span>;
1092-
}
1093-
1094-
render(<App x={s1} />, scratch);
1095-
expect(scratch.textContent).to.equal("1");
1096-
1097-
render(<App x={s2} />, scratch);
1098-
expect(scratch.textContent).to.equal("a");
1099-
1100-
s1.value = 2;
1101-
rerender();
1102-
expect(scratch.textContent).to.equal("a");
1103-
1104-
s2.value = "b";
1105-
rerender();
1106-
expect(scratch.textContent).to.equal("b");
1107-
});
1108-
1109-
it("should not recompute when the compute function doesn't change and dependency values don't change", async () => {
1110-
const s1 = signal(1);
1111-
const spy = sinon.spy();
1112-
1113-
function App({ x }: { x: Signal }) {
1114-
const fn = useCallback(() => {
1115-
spy();
1116-
return x.value;
1117-
}, [x]);
1118-
1119-
const c = useComputed(fn);
1120-
return <span>{c.value}</span>;
1121-
}
1122-
1123-
render(<App x={s1} />, scratch);
1124-
expect(scratch.textContent).to.equal("1");
1125-
expect(spy).to.have.been.calledOnce;
1126-
1127-
rerender();
1128-
expect(scratch.textContent).to.equal("1");
1129-
expect(spy).to.have.been.calledOnce;
1130-
});
1131-
});
11321073
});

packages/react/README.md

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -103,34 +103,6 @@ function Counter() {
103103
}
104104
```
105105

106-
> **Optimizing `useComputed` performance**
107-
>
108-
> The `useComputed` hook follows the convention of keeping closures fresh. By default, the callback function passed to `useComputed` will re-run on every component render to ensure it always has access to the latest values from the component's scope. However, if the computed value doesn't change, updates won't propagate to dependent nodes in the signal graph.
109-
>
110-
> For expensive computations, you can optimize performance by memoizing the callback with `useCallback`:
111-
>
112-
> ```js
113-
> import { useSignal, useComputed } from "@preact/signals-react";
114-
> import { useCallback } from "react";
115-
>
116-
> function Counter() {
117-
> const count = useSignal(0);
118-
>
119-
> // Memoize the callback to avoid re-running expensive calculations
120-
> const expensiveComputation = useCallback(() => {
121-
> for (let i = 0; i < 10000000; i++) {
122-
> result += 1;
123-
> }
124-
> return result;
125-
> // Empty deps means callback never changes, alternatively add count here so that if the identity of the signal changes this re-runs
126-
> }, []);
127-
>
128-
> const computed = useComputed(expensiveComputation);
129-
>
130-
> return <div>Result: {computed.value}</div>;
131-
> }
132-
> ```
133-
134106
### Using signals with React's SSR APIs
135107

136108
Components rendered using SSR APIs (e.g. `renderToString`) in a server environment (i.e. an environment without a global `window` object) will not track signals used during render. Components generally don't rerender when using SSR APIs so tracking signal usage is useless since changing these signals can't trigger a rerender.

0 commit comments

Comments
 (0)