diff --git a/README.md b/README.md index 25fd3059..d97c6fb8 100644 --- a/README.md +++ b/README.md @@ -1599,22 +1599,62 @@ When you don't know what to choose, prefer runtime checking and throwing over ty #### forwardRef/createRef -Check the [Hooks section](https://github.com/typescript-cheatsheets/react/blob/main/README.md#hooks) for `useRef`. +For `useRef`, check the [Hooks section](/docs/basic/getting-started/hooks#useref). -`createRef`: +#### Ref as a Prop (Recommended for React 19+) + +In React 19+, you can access `ref` directly as a prop in function components - no `forwardRef` wrapper needed. + +##### Option 1: Inherit all props from a native element + +Use `ComponentPropsWithRef` to inherit all props from a native element. ```tsx -import { createRef, PureComponent } from "react"; +import { ComponentPropsWithRef, useRef } from "react"; -class CssThemeProvider extends PureComponent { - private rootRef = createRef(); // like this - render() { - return
{this.props.children}
; - } +function MyInput(props: ComponentPropsWithRef<"input">) { + return ; +} + +// Usage in parent component +function Parent() { + const inputRef = useRef(null); + + return ; } ``` -`forwardRef`: +##### Option 2: Explicit typing + +If you have custom props and want fine-grained control, you can explicitly type the ref: + +```tsx +import { Ref, useRef } from "react"; + +interface MyInputProps { + placeholder: string; + ref: Ref; +} + +function MyInput(props: MyInputProps) { + return ; +} + +// Usage in parent component +function Parent() { + const inputRef = useRef(null); + + return ; +} +``` + +**Read more**: [Wrapping/Mirroring a HTML Element](/docs/advanced/patterns_by_usecase#wrappingmirroring-a-html-element) + +#### Legacy Approaches (Pre-React 19) + +##### forwardRef + +For React 18 and earlier, use `forwardRef`: ```tsx import { forwardRef, ReactNode } from "react"; @@ -1648,7 +1688,7 @@ interface Props { export const FancyButton = forwardRef( ( props: Props, - ref: Ref // <-- here! + ref: Ref // <-- explicit immutable ref type ) => ( + {item} ))} @@ -1703,17 +1748,19 @@ export function ClickableList(props: ClickableListProps) { } ``` -##### Option 2 - Redeclare forwardRef +##### Option 2: Redeclare forwardRef -```ts -// Redeclare forwardRef +For true `forwardRef` behavior with generics, extend the module declaration: + +```tsx +// Redeclare forwardRef to support generics declare module "react" { function forwardRef( render: (props: P, ref: React.Ref) => React.ReactElement | null ): (props: P & React.RefAttributes) => React.ReactElement | null; } -// Just write your components like you're used to! +// Now you can use forwardRef with generics normally import { forwardRef, ForwardedRef } from "react"; interface ClickableListProps { @@ -1729,7 +1776,7 @@ function ClickableListInner(
    {props.items.map((item, i) => (
  • - + {item}
  • ))} @@ -1740,10 +1787,12 @@ function ClickableListInner( export const ClickableList = forwardRef(ClickableListInner); ``` -##### Option 3 - Call signature +##### Option 3: Call Signature -```ts -// Add to `index.d.ts` +If you need both generic support and proper forwardRef behavior with full type inference, you can use the call signature: + +```tsx +// Add to your type definitions (e.g. in `index.d.ts` file) interface ForwardRefWithGenerics extends React.FC> { (props: WithForwardRefProps): ReturnType< React.FC> @@ -1754,15 +1803,20 @@ export const ClickableListWithForwardRef: ForwardRefWithGenerics = forwardRef(ClickableList); ``` -Credits: https://stackoverflow.com/a/73795494 +Credits: [https://stackoverflow.com/a/73795494](https://stackoverflow.com/a/73795494) -#### More Info +:::note +Option 1 is usually sufficient and clearer. Use Option 2 when you specifically need `forwardRef` behavior. Use Option 3 for advanced library scenarios requiring both generics and full forwardRef type inference. +::: -- https://medium.com/@martin_hotell/react-refs-with-typescript-a32d56c4d315 +#### Additional Resources -You may also wish to do [Conditional Rendering with `forwardRef`](https://github.com/typescript-cheatsheets/react/issues/167). +- [React refs with TypeScript](https://medium.com/@martin_hotell/react-refs-with-typescript-a32d56c4d315) +- [Conditional rendering with forwardRef](https://github.com/typescript-cheatsheets/react/issues/167) -[Something to add? File an issue](https://github.com/typescript-cheatsheets/react/issues/new). +--- + +[Something to add? File an issue](https://github.com/typescript-cheatsheets/react/issues/new) diff --git a/docs/basic/getting-started/forward-create-ref.md b/docs/basic/getting-started/forward-create-ref.md index be1015b3..282256f4 100644 --- a/docs/basic/getting-started/forward-create-ref.md +++ b/docs/basic/getting-started/forward-create-ref.md @@ -3,22 +3,62 @@ id: forward_and_create_ref title: forwardRef/createRef --- -Check the [Hooks section](https://github.com/typescript-cheatsheets/react/blob/main/README.md#hooks) for `useRef`. +For `useRef`, check the [Hooks section](/docs/basic/getting-started/hooks#useref). -`createRef`: +## Ref as a Prop (Recommended for React 19+) + +In React 19+, you can access `ref` directly as a prop in function components - no `forwardRef` wrapper needed. + +### Option 1: Inherit all props from a native element + +Use `ComponentPropsWithRef` to inherit all props from a native element. ```tsx -import { createRef, PureComponent } from "react"; +import { ComponentPropsWithRef, useRef } from "react"; -class CssThemeProvider extends PureComponent { - private rootRef = createRef(); // like this - render() { - return
    {this.props.children}
    ; - } +function MyInput(props: ComponentPropsWithRef<"input">) { + return ; +} + +// Usage in parent component +function Parent() { + const inputRef = useRef(null); + + return ; +} +``` + +### Option 2: Explicit typing + +If you have custom props and want fine-grained control, you can explicitly type the ref: + +```tsx +import { Ref, useRef } from "react"; + +interface MyInputProps { + placeholder: string; + ref: Ref; +} + +function MyInput(props: MyInputProps) { + return ; +} + +// Usage in parent component +function Parent() { + const inputRef = useRef(null); + + return ; } ``` -`forwardRef`: +**Read more**: [Wrapping/Mirroring a HTML Element](/docs/advanced/patterns_by_usecase#wrappingmirroring-a-html-element) + +## Legacy Approaches (Pre-React 19) + +### forwardRef + +For React 18 and earlier, use `forwardRef`: ```tsx import { forwardRef, ReactNode } from "react"; @@ -52,7 +92,7 @@ interface Props { export const FancyButton = forwardRef( ( props: Props, - ref: Ref // <-- here! + ref: Ref // <-- explicit immutable ref type ) => ( + {item} ))} @@ -107,17 +152,19 @@ export function ClickableList(props: ClickableListProps) { } ``` -### Option 2 - Redeclare forwardRef +### Option 2: Redeclare forwardRef + +For true `forwardRef` behavior with generics, extend the module declaration: -```ts -// Redeclare forwardRef +```tsx +// Redeclare forwardRef to support generics declare module "react" { function forwardRef( render: (props: P, ref: React.Ref) => React.ReactElement | null ): (props: P & React.RefAttributes) => React.ReactElement | null; } -// Just write your components like you're used to! +// Now you can use forwardRef with generics normally import { forwardRef, ForwardedRef } from "react"; interface ClickableListProps { @@ -133,7 +180,7 @@ function ClickableListInner(
      {props.items.map((item, i) => (
    • - + {item}
    • ))} @@ -144,10 +191,12 @@ function ClickableListInner( export const ClickableList = forwardRef(ClickableListInner); ``` -### Option 3 - Call signature +### Option 3: Call Signature + +If you need both generic support and proper forwardRef behavior with full type inference, you can use the call signature: -```ts -// Add to `index.d.ts` +```tsx +// Add to your type definitions (e.g. in `index.d.ts` file) interface ForwardRefWithGenerics extends React.FC> { (props: WithForwardRefProps): ReturnType< React.FC> @@ -158,12 +207,17 @@ export const ClickableListWithForwardRef: ForwardRefWithGenerics = forwardRef(ClickableList); ``` -Credits: https://stackoverflow.com/a/73795494 +Credits: [https://stackoverflow.com/a/73795494](https://stackoverflow.com/a/73795494) -## More Info +:::note +Option 1 is usually sufficient and clearer. Use Option 2 when you specifically need `forwardRef` behavior. Use Option 3 for advanced library scenarios requiring both generics and full forwardRef type inference. +::: -- https://medium.com/@martin_hotell/react-refs-with-typescript-a32d56c4d315 +## Additional Resources -You may also wish to do [Conditional Rendering with `forwardRef`](https://github.com/typescript-cheatsheets/react/issues/167). +- [React refs with TypeScript](https://medium.com/@martin_hotell/react-refs-with-typescript-a32d56c4d315) +- [Conditional rendering with forwardRef](https://github.com/typescript-cheatsheets/react/issues/167) + +--- -[Something to add? File an issue](https://github.com/typescript-cheatsheets/react/issues/new). +[Something to add? File an issue](https://github.com/typescript-cheatsheets/react/issues/new) diff --git a/docs/react-types/ComponentProps.md b/docs/react-types/ComponentProps.md index 613840a9..13c67ac9 100644 --- a/docs/react-types/ComponentProps.md +++ b/docs/react-types/ComponentProps.md @@ -2,12 +2,12 @@ title: ComponentProps --- -`ComponentProps` constructs a type with all valid props of an element or inferred props of a component. +`ComponentProps` constructs a type with all valid props of an element or inferred props of a component. It's an alias for `ComponentPropsWithRef`. :::note +**React 19+**: `ComponentPropsWithRef` is recommended as refs are now passed as props in function components. (See [forwardRef/createRef](/docs/basic/getting-started/forward_and_create_ref)) -Prefer `ComponentPropsWithRef` if ref is forwarded and `ComponentPropsWithoutRef` when ref is not forwarded. - +**React ≤18**: Prefer `ComponentPropsWithRef` if ref is forwarded and `ComponentPropsWithoutRef` when ref is not forwarded. ::: ## Parameters