diff --git a/docs/strict-typescript-api.md b/docs/strict-typescript-api.md index a426511835b..88b0325dcdc 100644 --- a/docs/strict-typescript-api.md +++ b/docs/strict-typescript-api.md @@ -1,62 +1,118 @@ --- id: strict-typescript-api -title: Strict TypeScript API (opt in) +title: Strict TypeScript API --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; import RNRepoLink from '@site/core/RNRepoLink'; -The Strict TypeScript API is a preview of our future, stable JavaScript API for React Native. +:::info[New in 0.87] -Specifically, this is a new set of TypeScript types for the `react-native` npm package, available from 0.80 onwards. These provide stronger and more futureproof type accuracy, and will allow us to confidently evolve React Native's API into a stable shape. Opting in to the Strict TypeScript API brings some structural type differences, and is therefore a one-time breaking change. +**The Strict API is now enabled by default** (previously an opt-in from 0.80+). This is a **breaking change**, detailed in the guide below. -The new types are: +::: -1. **Generated directly from our source code** — improving coverage and correctness, so you can expect stronger compatibility guarantees. -2. **Restricted to `react-native`'s index file** — more tightly defining our public API, and meaning we won't break the API when making internal file changes. +The Strict TypeScript API is React Native's modern TypeScript API, replacing the previous hand-maintained TypeScript definitions from earlier versions. -When the community is ready, the Strict TypeScript API will become our default API in future — synchronized with deep imports removal. +### Key changes (breaking) -## Opting in +1. **No deep imports.** The API is restricted to `react-native`'s index file. This is a tighter and more intentional public API contract. It also ensures that internal file path changes in React Native's source code won't be breaking. +2. **Generated directly from source.** Previously, React Native used separately maintained manual types. Generating from source now means we improve coverage, correctness, and compatibility guarantees. -We're shipping these new types alongside our existing types, meaning you can choose to migrate when ready. We encourage early adopters and newly created apps to opt in via your `tsconfig.json` file. +### Opting out
Since 0.87
-Opting in is a **breaking change**, since some of our new types have updated names and shapes, although many apps won't be affected. You can learn about each breaking change in the next section. +The Strict API is a **breaking change**, and not all apps and libraries may be able to migrate immediately. -```json title="tsconfig.json" -{ - "extends": "@react-native/typescript-config", - "compilerOptions": { - ... - "customConditions": ["react-native-strict-api"] +We continue to ship our previous manual types, which can be reverted to via your `tsconfig.json` config. Please note that in a future release we will remove this opt out. + +```diff title="tsconfig.json" + { + "extends": "@react-native/typescript-config", + "compilerOptions": { + ... ++ "customConditions": ["react-native", "react-native-legacy-deep-imports"] + } } -} ``` -:::note[Under the hood] +:::info[Issues / Feedback?] -This will instruct TypeScript to resolve `react-native` types from our new [`types_generated/`](https://www.npmjs.com/package/react-native?activeTab=code) dir, instead of the previous [`types/`](https://www.npmjs.com/package/react-native?activeTab=code) dir (manually maintained). No restart of TypeScript or your editor is required. +The 0.87 rollout of the Strict API by default is an ecosystem-wide change, and one we need to make in order to have stable API guarantees for React Native in future. + +Design choices about which APIs are exported at root, and intentional updates to type names/shapes have been worked on with the community and partners since the original 0.80 preview. + +We believe we've ironed out all rough edges, but there may still be edge cases. If you're choosing to opt out of the Strict API in 0.87 or later, we'd love to know why: [**discussion thread**](https://github.com/react-native-community/discussions-and-proposals/discussions/1015). ::: -The Strict TypeScript API follows our [RFC](https://github.com/react-native-community/discussions-and-proposals/pull/894) to remove deep imports from React Native. Therefore, some APIs are no longer exported at root. This is intentional, in order to reduce the overall surface area of React Native's API. +--- + +## Migration guide -:::tip[API feedback] +:::tip -**Sending feedback**: We will be working with the community to finalize which APIs we export over (at least) the next two React Native releases. Please share your feedback in our [feedback thread](https://github.com/react-native-community/discussions-and-proposals/discussions/893). +Use the [**/migrate-to-strict-api**](https://www.skills.sh/react-native-community/skills/migrate-to-strict-api) skill to migrate your codebase via an agent. -See also our [announcement blog post](/blog/2025/06/12/moving-towards-a-stable-javascript-api) for more info on our motivation and timelines. +```sh +npx skills add react-native-community/skills --skill migrate-to-strict-api +``` ::: -## Migration guide +### Before you start + +Enabling the Strict API — by upgrading to 0.87, or by opting in on an earlier release — affects TypeScript analysis of your own project only, scoped by its `tsconfig.json`. + +In most cases, codebases can migrate independently — app developers don't need to wait for specific dependencies to opt in, and library authors don't need to wait for their users. + +#### Keep `skipLibCheck` enabled + +The above depends on `skipLibCheck`, which `@react-native/typescript-config` enables by default. This keeps errors originating inside third-party `.d.ts` files out of your results. If your project overrides it, turn it back on before you start — otherwise you will see errors in dependencies that you can't fix. + +#### Update your dependencies + +Occasionally, your dependencies can have rough edges under the Strict API. Whenever type errors involve a library, check for and update to a fixed release first. + +One specific rough edge: Some libraries ship raw TypeScript source that your project imports, e.g. Jest setup files. These are typechecked as part of your project. Popular libraries have already repackaged these entry points: + +- `@expensify/react-native-live-markdown` — fixed in [0.1.335](https://github.com/Expensify/react-native-live-markdown/pull/771) +- `react-native-safe-area-context` — fixed in [5.8.1](https://github.com/AppAndFlow/react-native-safe-area-context/pull/745) + +
+**Advanced: Excluding an incompatible library** + +If an incompatible library is producing errors under `node_modules` (typically `TS2307: Cannot find module 'react-native/Libraries/...'`), you can exclude it from TypeScript analysis as a local fix, by redirecting the imported subpath to an untyped stub: -### Codegen types should now be imported from the `react-native` package +```json title="tsconfig.json" +{ + "compilerOptions": { + "paths": { + "some-library/jest/mock": ["./untyped-module.d.ts"] + } + } +} +``` + +```ts title="untyped-module.d.ts" +declare const anyExport: unknown; +export default anyExport; +``` + +Please also report the incompatibility to the library — the fixes linked above are a good template. + +
+ +### New `CodegenTypes` namespace Types used for codegen, like `Int32`, `Double`, `WithDefault` etc. are now available under a single `CodegenTypes` namespace. Similarly, `codegenNativeComponent` and `codegenNativeCommands` are now available to import from the react-native package instead of using the deep import. Namespaced `CodegenTypes` as well as `codegenNativeCommands` and `codegenNativeComponent` are also available from `react-native` package when the Strict API is not enabled to make the adoption easier for third-party libraries. -**Before** +#### Migration + + + ```ts title="" import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; @@ -75,7 +131,8 @@ export default codegenNativeComponent( ); ``` -**After** + + ```ts title="" import {CodegenTypes, codegenNativeComponent} from 'react-native'; @@ -90,9 +147,129 @@ export default codegenNativeComponent( ); ``` + + + +### Refs now use `*Instance` types
Since 0.87
+ +Each built-in component now has a dedicated `*Instance` type for use with refs — for example, `ViewInstance`, `TextInputInstance`, `ScrollViewInstance`. These are the **recommended way to type refs** under the Strict TypeScript API. + +Previously, `useRef` worked because `View` and other components were typed as a class. Under the Strict API, built-in components are typed as functions, so `View` refers to the function itself — **component type names no longer work as ref types**. + + + + +```tsx title="" +import {useRef} from 'react'; +import {View, TextInput} from 'react-native'; + +function MyComponent() { + const viewRef = useRef(null); + const inputRef = useRef(null); + + return ( + <> + + + + ); +} +``` + + + + +```tsx title="" +import {useRef} from 'react'; +import type { + TextInput, + TextInputInstance, + View, + ViewInstance, +} from 'react-native'; + +function MyComponent() { + const viewRef = useRef(null); + const inputRef = useRef(null); + + return ( + <> + + + + ); +} +``` + + + + +`*Instance` types also work transparently with `Animated` variants — no separate type is needed: + +```tsx title="" +const viewRef = useRef(null); + + + +``` + +This also replaces the removed `Animated.LegacyRef` type. Code using `ref={ref as React.Ref>}` can be simplified to `ref={ref}` with a `ViewInstance`-typed ref. + +
+**Available instance types** + +| Component | Instance type | +| ------------------------- | --------------------------------- | +| `ActivityIndicator` | `ActivityIndicatorInstance` | +| `Button` | `ButtonInstance` | +| `DrawerLayoutAndroid` | `DrawerLayoutAndroidInstance` | +| `FlatList` | `FlatListInstance` | +| `Image` | `ImageInstance` | +| `ImageBackground` | `ImageBackgroundInstance` | +| `KeyboardAvoidingView` | `KeyboardAvoidingViewInstance` | +| `Modal` | `ModalInstance` | +| `Pressable` | `PressableInstance` | +| `ProgressBarAndroid` | `ProgressBarAndroidInstance` | +| `RefreshControl` | `RefreshControlInstance` | +| `SafeAreaView` | `SafeAreaViewInstance` | +| `ScrollView` | `ScrollViewInstance` | +| `SectionList` | `SectionListInstance` | +| `StatusBar` | `StatusBarInstance` | +| `Switch` | `SwitchInstance` | +| `Text` | `TextInstance` | +| `TextInput` | `TextInputInstance` | +| `TouchableHighlight` | `TouchableHighlightInstance` | +| `TouchableNativeFeedback` | `TouchableNativeFeedbackInstance` | +| `TouchableOpacity` | `TouchableOpacityInstance` | +| `View` | `ViewInstance` | +| `VirtualizedList` | `VirtualizedListInstance` | +| `VirtualizedSectionList` | `VirtualizedSectionListInstance` | + +Components without ref support (`InputAccessoryView`, `TouchableWithoutFeedback`, `experimental_LayoutConformance`) do not have instance types. + +
+ +**Migration** + +| Before | After | +| ------------------------------------------------------- | ---------------------------- | +| `useRef(null)` | `useRef(null)` | +| `useRef>(null)` | `useRef(null)` | +| `useRef(null)` (for a specific component) | `useRef(null)` | +| `Ref>` | `Ref` | + +:::note + +`React.ComponentRef` remains valid and produces the same type as `ViewInstance`. The `*Instance` types are convenient aliases — both approaches work. + +::: + ### Removal of `*Static` types -**Before** +#### Migration + + + ```tsx title="" import {Linking, LinkingStatic} from 'react-native'; @@ -101,7 +278,8 @@ function foo(linking: LinkingStatic) {} foo(Linking); ``` -**After** + + ```tsx title="" import {Linking} from 'react-native'; @@ -110,11 +288,13 @@ function foo(linking: Linking) {} foo(Linking); ``` -The following APIs were previously named as `*Static` plus a variable declaration of said type. In most cases there was an alias so that value and the type were exported under the same identifier, but some were missing. + + -(For example there was an `AlertStatic` type, `Alert` variable of type `AlertStatic` and type `Alert` which was an alias for `AlertStatic`. But in the case of `PixelRatio` there was a `PixelRatioStatic` type and a `PixelRatio` variable of that type without additional type aliases.) +The following APIs were previously named as `*Static` plus a variable declaration of said type. In most cases there was an alias so that value and the type were exported under the same identifier, but some were missing. -**Affected APIs** +
+**Affected APIs** - `AlertStatic` - `ActionSheetIOSStatic` @@ -145,34 +325,39 @@ The following APIs were previously named as `*Static` plus a variable declaratio - `SettingsStatic` - `VibrationStatic` -### Some core components are now function components instead of class components +
-- `View` -- `Image` -- `TextInput` -- `Modal` -- `Text` -- `TouchableWithoutFeedback` -- `Switch` -- `ActivityIndicator` -- `ProgressBarAndroid` -- `InputAccessoryView` -- `Button` -- `SafeAreaView` +### Updating test mocks -Due to this change, accessing ref types of these views requires using `React.ComponentRef` pattern which works as expected for both class and function components, e.g.: +In most projects, no changes are needed here. Existing `jest.mock()` calls against `react-native/*` paths keep working — the Strict API doesn't change how module paths resolve in Jest or Metro, and a `jest.mock()` path string isn't type-checked. -```ts title="" -const ref = useRef>(null); +If a test file imports a deep path as a module (for example, to wrap the real implementation from `jest.requireActual()`), TypeScript will report the import as untyped. Where no root export covers the use case, you can keep the import and quiet the error: + +```ts +// @ts-expect-error - React Native internal, untyped under the Strict API +import NativeAppState from 'react-native/Libraries/AppState/NativeAppState'; ``` +Separately, if your Jest setup imports `react-native/Libraries/Core/InitializeCore`, update it — see [`InitializeCore` is now `react-native/setup-env`](#initializecore-is-now-react-nativesetup-env-since-087). + ## Other breaking changes +### `InitializeCore` is now `react-native/setup-env`
Since 0.87
+ +Unlike other deep imports, this module is a side-effect entry point with no root `react-native` equivalent. `InitializeCore` is deprecated since 0.87. + +```diff title="" +- import 'react-native/Libraries/Core/InitializeCore'; ++ import 'react-native/setup-env'; +``` + +Most apps never import this directly — it's typically found in Jest setup files and custom entry points. + ### Changes to Animated types Animated nodes were previously generic types based on their interpolation output. Now, they are non-generic types with a generic `interpolate` method. -`Animated.LegacyRef` is no longer available. +`Animated.LegacyRef` is no longer available. Use the appropriate `*Instance` type instead (e.g. `ViewInstance` for `Animated.View`). ### Unified types for optional props @@ -180,14 +365,86 @@ In the new types, every optional prop will be typed as `type | undefined`. ### Removal of some deprecated types -All types listed in `DeprecatedPropertiesAlias.d.ts` are inaccessible under the Strict API. +The long-deprecated `*Properties` aliases are inaccessible under the Strict API. These date from an early rename of the props types to their modern `*Props` names, and each alias has a direct replacement: `ViewProperties` becomes `ViewProps`, `TextInputProperties` becomes `TextInputProps`, and so on (plus `ImagePropertiesSourceOptions`, which becomes `ImageSourcePropType`). + +For the full list of aliases, see `DeprecatedPropertiesAlias.d.ts`. ### Removal of leftover component props Some properties that were defined in type definitions but were not used by the component or were lacking a definition were removed (for example: `lineBreakMode` on `Text`, `scrollWithoutAnimationTo` on `ScrollView`, transform styles defined outside of transform array). -### Previously accessible private type helpers may now be removed +### Removal of internal-only helper types Due to the configuration of the previous type definitions, every defined type was accessible from the `react-native` package. This included types that were not explicitly exported and helper types that were only supposed to be used internally. Notable examples of this are types related to StyleSheet (like `RecursiveArray`, `RegisteredStyle` and `Falsy`) and Animated (like `WithAnimatedArray` and `WithAnimatedObject`). + +## FAQs + +
+**Does this change anything at runtime?** + +No. The Strict API changes which type definitions TypeScript resolves — both modes resolve the same JavaScript, and your bundle is unaffected. + +Note that 0.87 separately removes `react-native/src/private/*` from the package's exports, which does affect runtime. That change is independent of the Strict API. + +
+ +
+**I maintain a library. Do I need to migrate before my users can upgrade?** + +No — libraries and apps should be able to migrate independently. The Strict API is enabled per project, through each project's own `tsconfig.json`: an app adopting it doesn't affect your library, and your library adopting it doesn't affect your users. Consumers only ever see the type definitions your package ships. + +Two caveats: any raw TypeScript source you ship for consumers to import (such as a Jest mock entry point) is typechecked inside their projects, so it must not rely on deep imports — ship compiled output with `.d.ts` files instead (see [Update your dependencies](#update-your-dependencies)). And you should still plan to migrate your own source: the legacy types opt-out is temporary. + +
+ +
+**An API I use isn't exported from `react-native`. Is that a bug?** + +In most cases this is intentional rather than an oversight. Our [RFC](https://github.com/react-native-community/discussions-and-proposals/pull/894) scoped the public API to what `react-native`'s index file exports, so some previously reachable internals are now private. + +If you depend on something with no root equivalent, please tell us in the [discussion thread](https://github.com/react-native-community/discussions-and-proposals/discussions/1015). Where justified, we may promote APIs to the index export. + +
+ +
+**Why were React Native's manual types replaced?** + +React Native is authored in [Flow](https://flow.org/), not TypeScript. Its TypeScript types were previously community-contributed and hand-maintained (originating from `@types/react-native` on DefinitelyTyped), leaving correctness gaps — generating them from our source code ensures the types always match the implementation. + +See [Moving Towards a Stable JavaScript API](/blog/2025/06/12/moving-towards-a-stable-javascript-api) for the full rationale. + +
+ +:::note[Learn more] + +
+
+ + Watch the talk! + + + We shared a deep dive into our motivations and the work + behind the Strict TypeScript API at{' '} + App.js 2025. + + + View on YouTube + +
+ App.js 2025 Talk +
+ +:::