From 00a968933fe2a6de296e314cf8a065a01bb0ff54 Mon Sep 17 00:00:00 2001 From: ShaneK Date: Mon, 17 Aug 2026 11:12:03 -0700 Subject: [PATCH 1/3] docs(react): document typed overlay hook props and typescript 5.4 requirement --- docs/react/overlays.md | 12 +++++- docs/reference/support.md | 4 +- docs/updating/9-0.md | 37 +++++++++++++++++++ static/usage/v9/modal/controller/react.md | 2 +- .../v9/popover/presenting/controller/react.md | 4 +- 5 files changed, 53 insertions(+), 6 deletions(-) diff --git a/docs/react/overlays.md b/docs/react/overlays.md index 8de7b6c971a..578d8e8ed25 100644 --- a/docs/react/overlays.md +++ b/docs/react/overlays.md @@ -49,11 +49,21 @@ showAlert({ Overlay hooks that display additional custom components as part of their markup, such as [modals](https://ionicframework.com/docs/api/modal) and [popovers](https://ionicframework.com/docs/api/popover), take in a couple of additional parameters when initializing their hooks. The first parameter is the component you want your overlay to display, and the second is an object of additional props you want to pass into the component when it gets constructed: ```tsx -const [present, dismiss] = useIonModal(({ name }) =>
Hello {name}.
, { +const [present, dismiss] = useIonModal(({ name }: { name: string }) =>
Hello {name}.
, { name: 'Dave', }); ``` +That second argument, `componentProps`, is type checked against the props the component declares, so passing a prop the component does not accept, or omitting one it requires, is a compile error. The component must declare its props type; the hook does not infer it from `componentProps`. That can be an annotation on the parameter, as above, or on the component itself: + +```tsx +const Greeting: React.FC<{ name: string }> = ({ name }) =>
Hello {name}.
; + +const [present, dismiss] = useIonModal(Greeting, { name: 'Dave' }); +``` + +Passing a JSX element instead of a component binds the props to the element, and `componentProps` is not type checked. + ## Overlay Components Overlays can also be displayed by using components from `@ionic/react`. The components take a `isOpen` prop that you provide to control if the overlay is currently being displayed or not. When `isOpen` switches from true to false (and vise versa), Ionic will open/close the overlay with the appropriate animation. You can also supply any other additional config options as props to the overlay: diff --git a/docs/reference/support.md b/docs/reference/support.md index 944bc112f6c..3cbbb985036 100644 --- a/docs/reference/support.md +++ b/docs/reference/support.md @@ -68,13 +68,15 @@ Note that later versions of Ionic do not support iOS 13; refer to the [mobile su | Framework | Required React Version | TypeScript | | :-------: | :--------------------: | :--------: | -| v9 | v18+ | 3.7+ | +| v9 | v18+ | 5.4+[^5] | | v8 | v17+ | 3.7+ | | v7 | v17+ | 3.7+ | | v6 | v17+ | 3.7+ | | v5 | v16.8+ | 3.7+ | | v4 | v16.8+ | 3.7+ | +[^5]: Ionic React v9 requires TypeScript 5.4+. `useIonModal` and `useIonPopover` type `componentProps` against the component they are given, and their published type definitions use `NoInfer`, which TypeScript added in 5.4. This matches the minimum Ionic Angular v9 requires. + #### Ionic Vue | Framework | Required Vue Version | TypeScript | diff --git a/docs/updating/9-0.md b/docs/updating/9-0.md index 6c4081fb71b..c22a9ebf95c 100644 --- a/docs/updating/9-0.md +++ b/docs/updating/9-0.md @@ -30,6 +30,7 @@ Useful options: - `--dry-run` reports what would change without writing anything. - `--check` reports only and exits non-zero if any migration is still pending, for use in CI. +- `--experimental` also runs experimental migrations, which apply fixes that need review. - `--force` writes even if the working tree is dirty or the project is not a git repository. - `--no-format` skips the Prettier pass over the changed files. - `--no-install` skips the dependency reinstall. @@ -180,6 +181,42 @@ npm install react@latest react-dom@latest npm install @ionic/react@latest @ionic/react-router@latest ``` +#### TypeScript {#react-typescript} + +The `@ionic/react` package requires TypeScript 5.4 or later. Its type definitions use `NoInfer`, which TypeScript added in 5.4. This matches the minimum `@ionic/angular` v9 requires. + +#### Typed Overlay Hook Props + +The `useIonModal` and `useIonPopover` hooks now type `componentProps` against the component they are given, instead of accepting `any`. Passing props that do not match the component is a compile error, and `componentProps` is required when the component declares required props: + +```tsx +const Modal: React.FC<{ title: string }> = ({ title }) =>
{title}
; + +// Error: componentProps is required because 'title' is required +useIonModal(Modal); + +// Error: 'subtitle' does not exist on the component's props +useIonModal(Modal, { title: 'Hello', subtitle: 'Nope' }); + +// Correct +const [present, dismiss] = useIonModal(Modal, { title: 'Hello' }); +``` + +Omitting `componentProps` altogether rules out the component signature, so TypeScript reports a mismatch against the JSX element signature rather than naming the missing prop. + +This surfaces existing mistakes at build time rather than at runtime, so an app that was passing incorrect props will see new type errors. Fix the call sites to match the component. Components typed as `FC` stay permissive, so an app whose overlay components are untyped needs no changes. To pin the props type instead of relying on inference, pass it as a type argument: `useIonModal(Component, props)`. + +The prop types are inferred from the component, not from `componentProps`. A component defined inline needs its props annotated: + +```diff +- const [present, dismiss] = useIonModal(({ name }) =>
Hello {name}.
, { name: 'Dave' }); ++ const [present, dismiss] = useIonModal(({ name }: { name: string }) =>
Hello {name}.
, { name: 'Dave' }); +``` + +Running `npx @ionic/migrate --experimental` can annotate the inline case for you. The remaining call sites are reported rather than rewritten, since the correct fix depends on what the component is meant to accept. + +Passing a JSX element rather than a component behaves as before: props are bound to the element and `componentProps` is not type checked. + ### React Router 1. Ionic 9 supports React Router 6. Update to version 6 of React Router: diff --git a/static/usage/v9/modal/controller/react.md b/static/usage/v9/modal/controller/react.md index 0cb92e7ad36..5f1d0033c95 100644 --- a/static/usage/v9/modal/controller/react.md +++ b/static/usage/v9/modal/controller/react.md @@ -44,7 +44,7 @@ const ModalExample = ({ dismiss }: { dismiss: (data?: string | null | undefined function Example() { const [present, dismiss] = useIonModal(ModalExample, { - dismiss: (data: string, role: string) => dismiss(data, role), + dismiss: (data?: string | number | null, role?: string) => dismiss(data, role), }); const [message, setMessage] = useState('This modal example uses the modalController to present and dismiss modals.'); diff --git a/static/usage/v9/popover/presenting/controller/react.md b/static/usage/v9/popover/presenting/controller/react.md index d23a58cbef6..48043ab57ef 100644 --- a/static/usage/v9/popover/presenting/controller/react.md +++ b/static/usage/v9/popover/presenting/controller/react.md @@ -5,9 +5,7 @@ import { IonButton, IonContent, useIonPopover } from '@ionic/react'; const Popover = () => Hello World!; function Example() { - const [present, dismiss] = useIonPopover(Popover, { - onDismiss: (data: any, role: string) => dismiss(data, role), - }); + const [present] = useIonPopover(Popover); return ( Date: Tue, 18 Aug 2026 08:36:04 -0700 Subject: [PATCH 2/3] docs(updating): use mdx heading id syntax and link to the migration tool --- docs/updating/9-0.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/updating/9-0.md b/docs/updating/9-0.md index c22a9ebf95c..55cb3a94fb3 100644 --- a/docs/updating/9-0.md +++ b/docs/updating/9-0.md @@ -181,7 +181,7 @@ npm install react@latest react-dom@latest npm install @ionic/react@latest @ionic/react-router@latest ``` -#### TypeScript {#react-typescript} +#### TypeScript {/* #react-typescript */} The `@ionic/react` package requires TypeScript 5.4 or later. Its type definitions use `NoInfer`, which TypeScript added in 5.4. This matches the minimum `@ionic/angular` v9 requires. @@ -213,7 +213,7 @@ The prop types are inferred from the component, not from `componentProps`. A com + const [present, dismiss] = useIonModal(({ name }: { name: string }) =>
Hello {name}.
, { name: 'Dave' }); ``` -Running `npx @ionic/migrate --experimental` can annotate the inline case for you. The remaining call sites are reported rather than rewritten, since the correct fix depends on what the component is meant to accept. +Running the [migration tool](#automated-migration) with `npx @ionic/migrate --experimental` can annotate the inline case for you. The remaining call sites are reported rather than rewritten, since the correct fix depends on what the component is meant to accept. Passing a JSX element rather than a component behaves as before: props are bound to the element and `componentProps` is not type checked. From aac0f518b8aba8633437f9cc0899757b502c5978 Mon Sep 17 00:00:00 2001 From: ShaneK Date: Tue, 18 Aug 2026 12:00:56 -0700 Subject: [PATCH 3/3] docs(support): merge the typescript 5.4 footnotes and scope them to angular and react --- docs/reference/support.md | 6 ++---- docs/updating/9-0.md | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/reference/support.md b/docs/reference/support.md index 3cbbb985036..d15fdef52e6 100644 --- a/docs/reference/support.md +++ b/docs/reference/support.md @@ -56,7 +56,7 @@ The Ionic team has compiled a set of recommendations for using the Ionic Framewo [^1]: Angular 14.x supported starting in Ionic v6.1.9. Angular 15.x supported starting in Ionic v6.3.6. [^2]: Angular 17.x supported starting in Ionic v7.5.4. [^3]: Angular 18.x supported starting in Ionic v8.2.0. -[^4]: Ionic v9 supports TypeScript 5.4+ for compatibility with Angular 18. Using Angular 21 requires TypeScript 5.9 or later, and Angular 22 requires TypeScript 6.0 or later, per Angular's own requirements. +[^4]: `@ionic/angular` and `@ionic/react` require TypeScript 5.4+. Using Angular 21 requires TypeScript 5.9 or later, and Angular 22 requires TypeScript 6.0 or later, per Angular's own requirements. **Angular 13+ Support On Older Versions of iOS** @@ -68,15 +68,13 @@ Note that later versions of Ionic do not support iOS 13; refer to the [mobile su | Framework | Required React Version | TypeScript | | :-------: | :--------------------: | :--------: | -| v9 | v18+ | 5.4+[^5] | +| v9 | v18+ | 5.4+[^4] | | v8 | v17+ | 3.7+ | | v7 | v17+ | 3.7+ | | v6 | v17+ | 3.7+ | | v5 | v16.8+ | 3.7+ | | v4 | v16.8+ | 3.7+ | -[^5]: Ionic React v9 requires TypeScript 5.4+. `useIonModal` and `useIonPopover` type `componentProps` against the component they are given, and their published type definitions use `NoInfer`, which TypeScript added in 5.4. This matches the minimum Ionic Angular v9 requires. - #### Ionic Vue | Framework | Required Vue Version | TypeScript | diff --git a/docs/updating/9-0.md b/docs/updating/9-0.md index 55cb3a94fb3..b2b5efaf5f9 100644 --- a/docs/updating/9-0.md +++ b/docs/updating/9-0.md @@ -183,7 +183,7 @@ npm install @ionic/react@latest @ionic/react-router@latest #### TypeScript {/* #react-typescript */} -The `@ionic/react` package requires TypeScript 5.4 or later. Its type definitions use `NoInfer`, which TypeScript added in 5.4. This matches the minimum `@ionic/angular` v9 requires. +The `@ionic/react` package requires TypeScript 5.4 or later. Its type definitions use `NoInfer`, which TypeScript added in 5.4. #### Typed Overlay Hook Props