Skip to content

Convert select component types to interface in TS typegen (#56809)#56809

Open
huntie wants to merge 1 commit into
facebook:mainfrom
huntie:export-D104808984
Open

Convert select component types to interface in TS typegen (#56809)#56809
huntie wants to merge 1 commit into
facebook:mainfrom
huntie:export-D104808984

Conversation

@huntie
Copy link
Copy Markdown
Member

@huntie huntie commented May 13, 2026

Summary:

Motivation

Adds (preserves) compatibility of Nativewind and Expo Web type augmentations with the Strict TypeScript API.

  • These libraries rely on key React Native component types being defined as interface, not type, since TS module augmentation only supports interface declarations.
  • Previously, our opt-in generated TypeScript types (Strict TypeScript API) emitted all props types as type Foo = Readonly<...> ), matching Flow source. However, this API change vs our manual types (which predominantly used interface on props) breaks library compatibility in these cases.
  • This is very awkward to otherwise solve in user space — so opt for backwards compatibility in our new types.

Primer

TypeScript module augmentation lets a library extend an existing module's types without modifying the source.

e.g. In Nativewind (source):

declare module 'react-native' {
  interface ScrollViewProps extends ViewProps, ScrollViewPropsIOS, ScrollViewPropsAndroid, Touchable {
    contentContainerClassName?: string;
    indicatorClassName?: string;
  }
  interface FlatListProps<ItemT> extends VirtualizedListProps<ItemT> {
    columnWrapperClassName?: string;
  }
  interface ViewProps {
    className?: string;
  }
}

Expo's react-native-web.d.ts (source) also follows the same pattern across several component props types.

Changes

Add new build-types transform, implementing an opt-in /** build-types emit-as-interface */ annotation, converting eligible type aliases to interface declarations.

  • Changing the Flow source files directly isn't viable — Flow doesn't support interface extends Omit<>, and several of these types use Omit<> in their composition. The post-transform operates on the TypeScript output where interface extends Readonly<Omit<...>> is valid.

The following emitted types are updated:

Type Augmented by Source
ViewProps className? Nativewind, Expo
ScrollViewProps contentContainerClassName?, indicatorClassName? Nativewind
ImagePropsBase className? Expo
SwitchProps className? Expo
TouchableWithoutFeedbackProps className? Expo
InputAccessoryViewProps className? Expo

FlatListProps is not included in this PR: its bare intersection (no Readonly<> wrapper) has a conflicting fadingEdgeLength property across constituent types that cannot be expressed as a single extends clause without hitting TS2320. This will be addressed in a follow-up.

VirtualizedListWithoutRenderItemProps<T> in react-native/virtualized-lists is separately owned and not changed here.

Changelog:
[General][Changed] - Strict TypeScript API: Select component props types are now interface declarations, enabling module augmentation by libraries like NativeWind and Expo (preserve compatibility)

Differential Revision: D104808984

@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 13, 2026
@meta-codesync
Copy link
Copy Markdown

meta-codesync Bot commented May 13, 2026

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

@github-actions
Copy link
Copy Markdown

Warning

JavaScript API change detected

This PR commits an update to ReactNativeApi.d.ts, indicating a change to React Native's public JavaScript API.

  • Please include a clear changelog message.
  • This change will be subject to additional review.

This change was flagged as: POTENTIALLY_BREAKING

@meta-codesync meta-codesync Bot changed the title Convert select component types to interface in TS typegen Convert select component types to interface in TS typegen (#56809) May 14, 2026
@huntie huntie force-pushed the export-D104808984 branch from 5ac79cf to 06544f0 Compare May 14, 2026 10:21
huntie added a commit to huntie/react-native that referenced this pull request May 14, 2026
…6809)

Summary:

#### Motivation

Adds (preserves) compatibility of Nativewind and Expo Web type augmentations with the Strict TypeScript API.

- These libraries rely on key React Native component types being defined as `interface`, not `type`, since TS module augmentation only supports `interface` declarations. 
- Previously, our opt-in generated TypeScript types (Strict TypeScript API) emitted all props types as `type Foo = Readonly<...>` ), matching Flow source. However, this API change vs our manual types (which predominantly used `interface` on props) breaks library compatibility in these cases.
- This is very awkward to otherwise solve in user space — so opt for backwards compatibility in our new types.

#### Primer

TypeScript module augmentation lets a library extend an existing module's types without modifying the source.

e.g. In Nativewind ([source](https://www.nativewind.dev/docs/guides/third-party-components)):

```ts
declare module 'react-native' {
  interface ScrollViewProps extends ViewProps, ScrollViewPropsIOS, ScrollViewPropsAndroid, Touchable {
    contentContainerClassName?: string;
    indicatorClassName?: string;
  }
  interface FlatListProps<ItemT> extends VirtualizedListProps<ItemT> {
    columnWrapperClassName?: string;
  }
  interface ViewProps {
    className?: string;
  }
}
```

Expo's `react-native-web.d.ts` ([source](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts)) also follows the same pattern across several component props types.

#### Changes

Add new `build-types` transform, implementing an opt-in `/** build-types emit-as-interface */` annotation, converting eligible `type` aliases to `interface` declarations.

- Changing the Flow source files directly isn't viable — Flow doesn't support `interface extends Omit<>`, and several of these types use `Omit<>` in their composition. The post-transform operates on the TypeScript output where `interface extends Readonly<Omit<...>>` is valid.

The following emitted types are updated:

| Type | Augmented by | Source |
|---|---|---|
| `ViewProps` | `className?` | [Nativewind](https://www.nativewind.dev/docs/guides/third-party-components), [Expo](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts) |
| `ScrollViewProps` | `contentContainerClassName?`, `indicatorClassName?` | [Nativewind](https://www.nativewind.dev/docs/guides/third-party-components) |
| `ImagePropsBase` | `className?` | [Expo](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts) |
| `SwitchProps` | `className?` | [Expo](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts) |
| `TouchableWithoutFeedbackProps` | `className?` | [Expo](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts) |
| `InputAccessoryViewProps` | `className?` | [Expo](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts) |

`FlatListProps` is not included in this PR: its bare intersection (no `Readonly<>` wrapper) has a conflicting `fadingEdgeLength` property across constituent types that cannot be expressed as a single `extends` clause without hitting TS2320. This will be addressed in a follow-up.

`VirtualizedListWithoutRenderItemProps<T>` in `react-native/virtualized-lists` is separately owned and not changed here.

Changelog:
[General][Changed] - **Strict TypeScript API**: Select component props types are now `interface` declarations, enabling module augmentation by libraries like NativeWind and Expo (preserve compatibility)

Differential Revision: D104808984
@huntie huntie added the JS API refinements (2025) Follow-up items from our JS API changes in 0.80 (deep imports deprecation and Strict TypeScript API) label May 14, 2026
…6809)

Summary:

#### Motivation

Adds (preserves) compatibility of Nativewind and Expo Web type augmentations with the Strict TypeScript API.

- These libraries rely on key React Native component types being defined as `interface`, not `type`, since TS module augmentation only supports `interface` declarations. 
- Previously, our opt-in generated TypeScript types (Strict TypeScript API) emitted all props types as `type Foo = Readonly<...>` ), matching Flow source. However, this API change vs our manual types (which predominantly used `interface` on props) breaks library compatibility in these cases.
- This is very awkward to otherwise solve in user space — so opt for backwards compatibility in our new types.

#### Primer

TypeScript module augmentation lets a library extend an existing module's types without modifying the source.

e.g. In Nativewind ([source](https://www.nativewind.dev/docs/guides/third-party-components)):

```ts
declare module 'react-native' {
  interface ScrollViewProps extends ViewProps, ScrollViewPropsIOS, ScrollViewPropsAndroid, Touchable {
    contentContainerClassName?: string;
    indicatorClassName?: string;
  }
  interface FlatListProps<ItemT> extends VirtualizedListProps<ItemT> {
    columnWrapperClassName?: string;
  }
  interface ViewProps {
    className?: string;
  }
}
```

Expo's `react-native-web.d.ts` ([source](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts)) also follows the same pattern across several component props types.

#### Changes

Add new `build-types` transform, implementing an opt-in `/** build-types emit-as-interface */` annotation, converting eligible `type` aliases to `interface` declarations.

- Changing the Flow source files directly isn't viable — Flow doesn't support `interface extends Omit<>`, and several of these types use `Omit<>` in their composition. The post-transform operates on the TypeScript output where `interface extends Readonly<Omit<...>>` is valid.

The following emitted types are updated:

| Type | Augmented by | Source |
|---|---|---|
| `ViewProps` | `className?` | [Nativewind](https://www.nativewind.dev/docs/guides/third-party-components), [Expo](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts) |
| `ScrollViewProps` | `contentContainerClassName?`, `indicatorClassName?` | [Nativewind](https://www.nativewind.dev/docs/guides/third-party-components) |
| `ImagePropsBase` | `className?` | [Expo](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts) |
| `SwitchProps` | `className?` | [Expo](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts) |
| `TouchableWithoutFeedbackProps` | `className?` | [Expo](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts) |
| `InputAccessoryViewProps` | `className?` | [Expo](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts) |

`FlatListProps` is not included in this PR: its bare intersection (no `Readonly<>` wrapper) has a conflicting `fadingEdgeLength` property across constituent types that cannot be expressed as a single `extends` clause without hitting TS2320. This will be addressed in a follow-up.

`VirtualizedListWithoutRenderItemProps<T>` in `react-native/virtualized-lists` is separately owned and not changed here.

Changelog:
[General][Changed] - **Strict TypeScript API**: Select component props types are now `interface` declarations, enabling module augmentation by libraries like NativeWind and Expo (preserve compatibility)

Differential Revision: D104808984
@huntie huntie force-pushed the export-D104808984 branch from 06544f0 to 5bd673b Compare May 14, 2026 15:30
huntie added a commit to huntie/react-native that referenced this pull request May 14, 2026
…6809)

Summary:

#### Motivation

Adds (preserves) compatibility of Nativewind and Expo Web type augmentations with the Strict TypeScript API.

- These libraries rely on key React Native component types being defined as `interface`, not `type`, since TS module augmentation only supports `interface` declarations. 
- Previously, our opt-in generated TypeScript types (Strict TypeScript API) emitted all props types as `type Foo = Readonly<...>` ), matching Flow source. However, this API change vs our manual types (which predominantly used `interface` on props) breaks library compatibility in these cases.
- This is very awkward to otherwise solve in user space — so opt for backwards compatibility in our new types.

#### Primer

TypeScript module augmentation lets a library extend an existing module's types without modifying the source.

e.g. In Nativewind ([source](https://www.nativewind.dev/docs/guides/third-party-components)):

```ts
declare module 'react-native' {
  interface ScrollViewProps extends ViewProps, ScrollViewPropsIOS, ScrollViewPropsAndroid, Touchable {
    contentContainerClassName?: string;
    indicatorClassName?: string;
  }
  interface FlatListProps<ItemT> extends VirtualizedListProps<ItemT> {
    columnWrapperClassName?: string;
  }
  interface ViewProps {
    className?: string;
  }
}
```

Expo's `react-native-web.d.ts` ([source](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts)) also follows the same pattern across several component props types.

#### Changes

Add new `build-types` transform, implementing an opt-in `/** build-types emit-as-interface */` annotation, converting eligible `type` aliases to `interface` declarations.

- Changing the Flow source files directly isn't viable — Flow doesn't support `interface extends Omit<>`, and several of these types use `Omit<>` in their composition. The post-transform operates on the TypeScript output where `interface extends Readonly<Omit<...>>` is valid.

The following emitted types are updated:

| Type | Augmented by | Source |
|---|---|---|
| `ViewProps` | `className?` | [Nativewind](https://www.nativewind.dev/docs/guides/third-party-components), [Expo](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts) |
| `ScrollViewProps` | `contentContainerClassName?`, `indicatorClassName?` | [Nativewind](https://www.nativewind.dev/docs/guides/third-party-components) |
| `ImagePropsBase` | `className?` | [Expo](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts) |
| `SwitchProps` | `className?` | [Expo](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts) |
| `TouchableWithoutFeedbackProps` | `className?` | [Expo](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts) |
| `InputAccessoryViewProps` | `className?` | [Expo](https://unpkg.com/expo@54.0.31/types/react-native-web.d.ts) |

`FlatListProps` is not included in this PR: its bare intersection (no `Readonly<>` wrapper) has a conflicting `fadingEdgeLength` property across constituent types that cannot be expressed as a single `extends` clause without hitting TS2320. This will be addressed in a follow-up.

`VirtualizedListWithoutRenderItemProps<T>` in `react-native/virtualized-lists` is separately owned and not changed here.

Changelog:
[General][Changed] - **Strict TypeScript API**: Select component props types are now `interface` declarations, enabling module augmentation by libraries like NativeWind and Expo (preserve compatibility)

Differential Revision: D104808984
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 JS API refinements (2025) Follow-up items from our JS API changes in 0.80 (deep imports deprecation and Strict TypeScript API) meta-exported p: Facebook Partner: Facebook Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant