Skip to content

Commit ad5a485

Browse files
Shu-JiFinnJoviDeCroock
authored
Support forwardRef in @preact/signals-react (#246)
* Support forwardRef in @preact/signals-react * Create thin-coats-wash.md --------- Co-authored-by: Finn <[email protected]> Co-authored-by: Jovi De Croock <[email protected]>
1 parent 8e726ed commit ad5a485

File tree

3 files changed

+37
-4
lines changed

3 files changed

+37
-4
lines changed

.changeset/thin-coats-wash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@preact/signals-react": patch
3+
---
4+
5+
Support forwardRef in @preact/signals-react

packages/react/src/index.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ export { signal, computed, batch, effect, Signal, type ReadonlySignal };
2424
const Empty = [] as const;
2525
const ReactElemType = Symbol.for("react.element"); // https://github.com/facebook/react/blob/346c7d4c43a0717302d446da9e7423a8e28d8996/packages/shared/ReactSymbols.js#L15
2626
const ReactMemoType = Symbol.for("react.memo"); // https://github.com/facebook/react/blob/346c7d4c43a0717302d446da9e7423a8e28d8996/packages/shared/ReactSymbols.js#L30
27+
const ReactForwardRefType = Symbol.for("react.forward_ref"); // https://github.com/facebook/react/blob/346c7d4c43a0717302d446da9e7423a8e28d8996/packages/shared/ReactSymbols.js#L25
2728
const ProxyInstance = new WeakMap<
2829
FunctionComponent<any>,
2930
FunctionComponent<any>
3031
>();
32+
3133
const SupportsProxy = typeof Proxy === "function";
3234

3335
const ProxyHandlers = {
@@ -161,9 +163,14 @@ function WrapJsx<T>(jsx: T): T {
161163
return jsx.call(jsx, ProxyFunctionalComponent(type), props, ...rest);
162164
}
163165

164-
if (type && typeof type === "object" && type.$$typeof === ReactMemoType) {
165-
type.type = ProxyFunctionalComponent(type.type);
166-
return jsx.call(jsx, type, props, ...rest);
166+
if (type && typeof type === "object") {
167+
if (type.$$typeof === ReactMemoType) {
168+
type.type = ProxyFunctionalComponent(type.type);
169+
return jsx.call(jsx, type, props, ...rest);
170+
} else if (type.$$typeof === ReactForwardRefType) {
171+
type.render = ProxyFunctionalComponent(type.render);
172+
return jsx.call(jsx, type, props, ...rest);
173+
}
167174
}
168175

169176
if (typeof type === "string" && props) {

packages/react/test/index.test.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
33

44
import { signal, useComputed, useSignalEffect } from "@preact/signals-react";
5-
import { createElement, useMemo, memo, StrictMode, createRef } from "react";
5+
import { createElement, forwardRef, useMemo, memo, StrictMode, createRef } from "react";
6+
67
import { createRoot, Root } from "react-dom/client";
78
import { renderToStaticMarkup } from "react-dom/server";
89
import { act } from "react-dom/test-utils";
@@ -160,6 +161,26 @@ describe("@preact/signals-react", () => {
160161
expect(scratch.textContent).to.equal("bar");
161162
});
162163

164+
it("should update forwardRef'ed component via signals", async () => {
165+
const sig = signal("foo");
166+
167+
const Inner = forwardRef(() => {
168+
return <p>{sig.value}</p>;
169+
});
170+
171+
function App() {
172+
return <Inner />;
173+
}
174+
175+
render(<App />);
176+
expect(scratch.textContent).to.equal("foo");
177+
178+
act(() => {
179+
sig.value = "bar";
180+
});
181+
expect(scratch.textContent).to.equal("bar");
182+
});
183+
163184
it("should consistently rerender in strict mode", async () => {
164185
const sig = signal<string>(null!);
165186

0 commit comments

Comments
 (0)