Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion docs/react/overlays.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => <div>Hello {name}.</div>, {
const [present, dismiss] = useIonModal(({ name }: { name: string }) => <div>Hello {name}.</div>, {
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 }) => <div>Hello {name}.</div>;

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:
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/support.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**

Expand All @@ -68,7 +68,7 @@ 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+[^4] |
| v8 | v17+ | 3.7+ |
| v7 | v17+ | 3.7+ |
| v6 | v17+ | 3.7+ |
Expand Down
37 changes: 37 additions & 0 deletions docs/updating/9-0.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.

#### 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 }) => <div>{title}</div>;

// 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<any>` 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<Props>(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 }) => <div>Hello {name}.</div>, { name: 'Dave' });
+ const [present, dismiss] = useIonModal(({ name }: { name: string }) => <div>Hello {name}.</div>, { name: 'Dave' });
```

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.

### React Router

1. Ionic 9 supports React Router 6. Update to version 6 of React Router:
Expand Down
2 changes: 1 addition & 1 deletion static/usage/v9/modal/controller/react.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.');

Expand Down
4 changes: 1 addition & 3 deletions static/usage/v9/popover/presenting/controller/react.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import { IonButton, IonContent, useIonPopover } from '@ionic/react';
const Popover = () => <IonContent className="ion-padding">Hello World!</IonContent>;

function Example() {
const [present, dismiss] = useIonPopover(Popover, {
onDismiss: (data: any, role: string) => dismiss(data, role),
});
const [present] = useIonPopover(Popover);

return (
<IonButton
Expand Down