) => ReactNode
+): FunctionComponent }>
```
The following functions do **not** have memoization or optimization, and are only useful if you are
diff --git a/src/index.ts b/src/index.ts
index 114b940..d93d46c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,14 @@
export { createRef, isRef } from "./ref"
export { useClassList, useText } from "./hooks"
-export { memo, StrictMode, useCallback, useMemo, useRef } from "./react-compat-api"
+export {
+ forwardRef,
+ memo,
+ StrictMode,
+ useCallback,
+ useImperativeHandle,
+ useMemo,
+ useRef,
+} from "./react-compat-api"
import { Component, Fragment, createElement } from "./jsx-dom"
import { ShadowRoot } from "./shadow"
diff --git a/src/jsx-dom.ts b/src/jsx-dom.ts
index 846ed7d..df11b4d 100644
--- a/src/jsx-dom.ts
+++ b/src/jsx-dom.ts
@@ -196,8 +196,8 @@ export function createElement(tag: any, attr: any, ...children: any[]) {
return jsx(tag, { ...attr, children }, attr.key)
}
-function attachRef(ref: any | undefined, node: Node) {
- if (isRef(ref)) {
+export function attachRef(ref: any | undefined, node: T) {
+ if (isRef(ref)) {
ref.current = node
} else if (isFunction(ref)) {
ref(node)
@@ -251,7 +251,7 @@ function style(node: Element & HTMLOrSVGElement, value?: any) {
node.setAttribute("style", value)
} else if (isObject(value)) {
forEach(value, (val, key) => {
- if (key.indexOf('-') === 0) {
+ if (key.indexOf("-") === 0) {
// CSS custom properties (variables) start with `-` (e.g. `--my-variable`)
// and must be assigned via `setProperty`.
cast(node).style.setProperty(key, val)
diff --git a/src/react-compat-api.ts b/src/react-compat-api.ts
index c346da9..97d9ee6 100644
--- a/src/react-compat-api.ts
+++ b/src/react-compat-api.ts
@@ -1,3 +1,7 @@
+import type { FunctionComponent, ReactElement, Ref } from "../types"
+import { attachRef } from "./jsx-dom"
+import { createRef } from "./ref"
+
export { createRef as useRef } from "./ref"
export { identity as useCallback, identity as memo } from "./util"
export { Fragment as StrictMode } from "./jsx-dom"
@@ -5,3 +9,13 @@ export { Fragment as StrictMode } from "./jsx-dom"
export function useMemo(factory: () => T): T {
return factory()
}
+
+export function forwardRef(
+ render: (props: P, ref: Ref) => ReactElement
+): FunctionComponent }> {
+ return ({ ref, ...props }) => render(props as P, ref ?? createRef())
+}
+
+export function useImperativeHandle(ref: Ref, init: () => T, _deps: unknown) {
+ attachRef(ref, init())
+}
diff --git a/src/ref.ts b/src/ref.ts
index 681a17b..8bde80c 100644
--- a/src/ref.ts
+++ b/src/ref.ts
@@ -1,6 +1,6 @@
import { isObject } from "./util"
-interface Ref {
+interface Ref {
current: null | T
}
@@ -8,6 +8,6 @@ export function createRef(): Ref {
return Object.seal({ current: null })
}
-export function isRef(maybeRef: any): maybeRef is Ref {
+export function isRef(maybeRef: any): maybeRef is Ref {
return isObject(maybeRef) && "current" in maybeRef
}
diff --git a/test/test-main.tsx b/test/test-main.tsx
index 26d9207..6469fe0 100644
--- a/test/test-main.tsx
+++ b/test/test-main.tsx
@@ -321,6 +321,62 @@ describe("jsx-dom", () => {
expect(button).to.be.instanceOf(HTMLButtonElement)
})
+ describe("supports forwardRef", () => {
+ it("element", () => {
+ const Container = React.forwardRef((props, ref) => (
+
+ Button
+
+ ))
+
+ const ref = lib.createRef()
+ const node = (
+
+ Click me!
+
+ )
+ expect(node.className).to.equal("container")
+ expect(ref.current).to.be.instanceOf(HTMLButtonElement)
+ })
+
+ it("component", () => {
+ const Button = props =>
+ const Container = React.forwardRef((props, ref) => (
+
+ Button
+
+ ))
+
+ const ref = lib.createRef()
+ const node = (
+
+ Click me!
+
+ )
+ expect(node.className).to.equal("container")
+ expect(ref.current).to.be.instanceOf(HTMLButtonElement)
+ })
+ })
+
+ it("supports useImperativeHandle", () => {
+ const Button = React.forwardRef<{ focus: () => string }, any>((props, ref) => {
+ React.useImperativeHandle(ref, () => ({
+ focus: () => "ping",
+ }))
+ return
+ })
+
+ const ref = lib.createRef()
+ const node = (
+
+ Click me!
+
+ )
+ expect(node.className).to.equal("container")
+ expect(ref.current).to.have.property("focus")
+ expect(ref.current.focus()).to.equal("ping")
+ })
+
it("supports defaultProps in functional components", () => {
const Button = (props: any) =>
Button.defaultProps = { className: "defaultClass" }
diff --git a/types/index.d.ts b/types/index.d.ts
index 33758e2..38d92d3 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -58,7 +58,7 @@ interface AttrWithRef extends Attributes {
ref?: Ref | undefined
}
-type ReactElement = HTMLElement | SVGElement
+export type ReactElement = HTMLElement | SVGElement
type DOMFactory, T extends Element> = (
props?: (AttrWithRef & P) | null,
@@ -255,6 +255,13 @@ export interface MutableRefObject {
export function createRef(): RefObject
+/**
+ * React compatibility-only API.
+ */
+export function forwardRef(
+ render: (props: P, ref: Ref) => ReactNode
+): FunctionComponent }>
+
/**
* `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
* (`initialValue`). The returned object will persist for the full lifetime of the component.
@@ -297,6 +304,8 @@ export function useRef(initialValue: T | null): RefObject
*/
export function useRef(): MutableRefObject
+export function useImperativeHandle(ref: Ref, init: () => T, deps?: DependencyList): void
+
// I made 'inputs' required here and in useMemo as there's no point to memoizing without the memoization key
// useCallback(X) is identical to just using X, useMemo(() => Y) is identical to just using Y.
/**