Skip to content

Fix ref type compatibility for builtin components (#56673)#56673

Open
huntie wants to merge 1 commit intofacebook:mainfrom
huntie:export-D103601923
Open

Fix ref type compatibility for builtin components (#56673)#56673
huntie wants to merge 1 commit intofacebook:mainfrom
huntie:export-D103601923

Conversation

@huntie
Copy link
Copy Markdown
Member

@huntie huntie commented May 3, 2026

Summary:

Fix useRef and forwardRef backcompat when migrating to React Native's generated TypeScript types (Strict TypeScript API).

Previously this was an awkward breaking change: https://reactnative.dev/docs/strict-typescript-api#some-core-components-are-now-function-components-instead-of-class-components

- const ref = useRef<View>(null);
+ const ref = useRef<React.ComponentRef<typeof View>>(null);

After this diff, no workaround will be required.

Changes

  • Add companion type aliases alongside each component's value export.
  • Introduces a new /* ts-only */ comment syntax in index.js.flow to hide the companion exports from Flow, which has no equivalent concept of declaration merging.

Problem detail

In our Flow source code, built-in components like View and TextInput are function components rather than classes. This differs from the previous manual .d.ts files which defined each component as a class.

In TypeScript, a class name serves as both a value (the constructor) and a type (the instance), so patterns like useRef<View>() work. However, with function components, the name is only a value (the function signature) making useRef<View>() resolve to a ref holding the function itself, not the native element.

The fix: Add companion type aliases alongside each component's value export. TypeScript's declaration merging makes the component name simultaneously a value (for JSX) and a type (for refs).

export {default as View} from './Libraries/Components/View/View';
export type View = _HostInstance;
  // Before — required workaround
- const ref = useRef<View>(null);          // TS2749: 'View' refers to a value, not a type
+ const ref = useRef<React.ComponentRef<typeof View>>(null);
  ref.current?.measure(...);

- const inputRef = useRef<TextInput>(null); // TS2749: 'TextInput' refers to a value, not a type
+ const inputRef = useRef<React.ComponentRef<typeof TextInput>>(null);
  inputRef.current?.focus();

  // After — original patterns work as-is
  const ref = useRef<View>(null);          // View as a type = HostInstance
  ref.current?.measure(...);               // HostInstance has measure()

  const inputRef = useRef<TextInput>(null); // TextInput as a type = TextInputInstance
  inputRef.current?.focus();               // TextInputInstance has focus()

Changelog:
[General][Fixed] - Add companion instance type aliases for built-in components, preserving useRef<Component> patterns under the Strict TypeScript API

Differential Revision: D103601923

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label May 3, 2026
@meta-codesync
Copy link
Copy Markdown

meta-codesync Bot commented May 3, 2026

@huntie has exported this pull request. If you are a Meta employee, you can view the originating Diff in D103601923.

@facebook-github-tools facebook-github-tools Bot added p: Facebook Partner: Facebook Partner labels May 3, 2026
@meta-codesync meta-codesync Bot changed the title Fix ref type compatibility for builtin components Fix ref type compatibility for builtin components (#56673) May 5, 2026
huntie added a commit to huntie/react-native that referenced this pull request May 5, 2026
Summary:

Fix `useRef` and `forwardRef` backcompat when migrating to React Native's generated TypeScript types (Strict TypeScript API).

Previously this was an awkward breaking change: https://reactnative.dev/docs/strict-typescript-api#some-core-components-are-now-function-components-instead-of-class-components

```diff
- const ref = useRef<View>(null);
+ const ref = useRef<React.ComponentRef<typeof View>>(null);
```

After this diff, no workaround will be required.

**Problem detail**

In our Flow source code, built-in components like `View` and `TextInput` are function components rather than classes. This differs from the previous manual `.d.ts` files which defined each component as a class.

In TypeScript, a class name serves as both a value (the constructor) and a type (the instance), so patterns like `useRef<View>()` work. However, with function components, the name is only a value (the function signature) making `useRef<View>()` resolve to a ref holding the function itself, not the native element.

**The fix**: Add companion type aliases alongside each component's value export. TypeScript's declaration merging makes the component name simultaneously a value (for JSX) and a type (for refs).

```diff
  // Before — required workaround
- const ref = useRef<View>(null);
+ const ref = useRef<React.ComponentRef<typeof View>>(null);
  ref.current?.measure(...);

- const inputRef = useRef<TextInput>(null); 
+ const inputRef = useRef<React.ComponentRef<typeof TextInput>>(null);
  inputRef.current?.focus();

  // After — original patterns work as-is
  const ref = useRef<View>(null);          // View as a type = HostInstance
  ref.current?.measure(...);               // HostInstance has measure()

  const inputRef = useRef<TextInput>(null); // TextInput as a type = TextInputInstance
  inputRef.current?.focus();                // TextInputInstance has focus()
```

Changelog:
[General][Fixed] - Add companion instance type aliases for built-in components, preserving `useRef<Component>` patterns under the Strict TypeScript API

Differential Revision: D103601923
@huntie huntie force-pushed the export-D103601923 branch from 6cb3c3d to 56e099a Compare May 5, 2026 12:24
Summary:

Fix `useRef` and `forwardRef` backcompat when migrating to React Native's generated TypeScript types (Strict TypeScript API).

Previously this was an awkward breaking change: https://reactnative.dev/docs/strict-typescript-api#some-core-components-are-now-function-components-instead-of-class-components

```diff
- const ref = useRef<View>(null);
+ const ref = useRef<React.ComponentRef<typeof View>>(null);
```

After this diff, no workaround will be required.

**Changes**

- Add companion type aliases alongside each component's value export.
- Introduces a new `/* ts-only */` comment syntax in `index.js.flow` to hide the companion exports from Flow, which has no equivalent concept of declaration merging.

**Problem detail**

In our Flow source code, built-in components like `View` and `TextInput` are function components rather than classes. This differs from the previous manual `.d.ts` files which [defined each component as a class](https://github.com/facebook/react-native/blob/d1cdce24cca2224f8d37873d24a49523a8271a0a/packages/react-native/Libraries/Text/Text.d.ts#L226).

In TypeScript, a class name serves as both a value (the constructor) and a type (the instance), so patterns like `useRef<View>()` work. However, with function components, the name is only a value (the function signature) making `useRef<View>()` resolve to a ref holding the function itself, not the native element.

**The fix**: Add companion type aliases alongside each component's value export. TypeScript's declaration merging makes the component name simultaneously a value (for JSX) and a type (for refs).

```diff
  // Before — required workaround
- const ref = useRef<View>(null);          // TS2749: 'View' refers to a value, not a type
+ const ref = useRef<React.ComponentRef<typeof View>>(null);
  ref.current?.measure(...);

- const inputRef = useRef<TextInput>(null); // TS2749: 'TextInput' refers to a value, not a type
+ const inputRef = useRef<React.ComponentRef<typeof TextInput>>(null);
  inputRef.current?.focus();

  // After — original patterns work as-is
  const ref = useRef<View>(null);          // View as a type = HostInstance
  ref.current?.measure(...);               // HostInstance has measure()

  const inputRef = useRef<TextInput>(null); // TextInput as a type = TextInputInstance
  inputRef.current?.focus();               // TextInputInstance has focus()
```

Changelog:
[General][Fixed] - Add companion instance type aliases for built-in components, preserving `useRef<Component>` patterns under the Strict TypeScript API

Differential Revision: D103601923
@huntie huntie force-pushed the export-D103601923 branch from 56e099a to 778ae87 Compare May 5, 2026 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. fb-exported meta-exported p: Facebook Partner: Facebook Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant