refactor(react): remove IonRedirect and update BREAKING.md for React Router v6

This commit is contained in:
ShaneK
2026-03-04 13:33:11 -08:00
parent 6f498a9da0
commit 70da68cf86
6 changed files with 112 additions and 60 deletions

View File

@@ -66,12 +66,62 @@ Routes that contain nested routes or child `IonRouterOutlet` components need a `
Route parameters are now accessed via the `useParams` hook instead of props:
```diff
- import { RouteComponentProps } from 'react-router-dom';
+ import { useParams } from 'react-router-dom';
- const MyComponent: React.FC<RouteComponentProps<{ id: string }>> = ({ match }) => {
- const id = match.params.id;
+ const MyComponent: React.FC = () => {
+ const { id } = useParams<{ id: string }>();
```
**RouteComponentProps Removed**
The `RouteComponentProps` type and its `history`, `location`, and `match` props are no longer available in React Router v6. Use the equivalent hooks instead:
- `history` -> `useNavigate` (see below) or `useIonRouter`
- `match.params` -> `useParams` (covered above)
- `location` -> `useLocation`
```diff
- import { RouteComponentProps } from 'react-router-dom';
+ import { useNavigate, useLocation } from 'react-router-dom';
+ import { useIonRouter } from '@ionic/react';
- const MyComponent: React.FC<RouteComponentProps> = ({ history, location }) => {
- history.push('/path');
- history.replace('/path');
- history.goBack();
- console.log(location.pathname);
+ const MyComponent: React.FC = () => {
+ const navigate = useNavigate();
+ const router = useIonRouter();
+ const location = useLocation();
+ // In an event handler or useEffect:
+ navigate('/path');
+ navigate('/path', { replace: true });
+ router.goBack();
+ console.log(location.pathname);
```
**Exact Prop Removed**
The `exact` prop is no longer needed. React Router v6 routes match exactly by default. To match sub-paths, use a `/*` suffix on the path:
```diff
- <Route path="/home" exact />
+ <Route path="/home" />
```
**Render Prop Removed**
The `render` prop has been replaced with the `element` prop:
```diff
- <Route path="/foo" render={(props) => <Foo {...props} />} />
+ <Route path="/foo" element={<Foo />} />
```
**Programmatic Navigation**
The `useHistory` hook has been replaced with `useNavigate`:
@@ -79,18 +129,71 @@ The `useHistory` hook has been replaced with `useNavigate`:
```diff
- import { useHistory } from 'react-router-dom';
+ import { useNavigate } from 'react-router-dom';
+ import { useIonRouter } from '@ionic/react';
- const history = useHistory();
+ const navigate = useNavigate();
+ const router = useIonRouter();
- history.goBack();
+ navigate(-1);
- history.push('/path');
+ navigate('/path');
- history.replace('/path');
+ navigate('/path', { replace: true });
- history.push('/path');
+ navigate('/path');
- history.goBack();
+ router.goBack();
```
For more information on migrating from React Router v5 to v6, refer to the [React Router v6 Upgrade Guide](https://reactrouter.com/en/main/upgrading/v5).
**Custom History Prop Removed**
The `history` prop has been removed from `IonReactRouter`, `IonReactHashRouter`, and `IonReactMemoryRouter`. React Router v6's `BrowserRouter`, `HashRouter`, and `MemoryRouter` no longer accept custom `history` objects.
```diff
- import { createBrowserHistory } from 'history';
- const history = createBrowserHistory();
- <IonReactRouter history={history}>
+ <IonReactRouter>
```
For `IonReactMemoryRouter` (commonly used in tests), use `initialEntries` instead:
```diff
- import { createMemoryHistory } from 'history';
- const history = createMemoryHistory({ initialEntries: ['/start'] });
- <IonReactMemoryRouter history={history}>
+ <IonReactMemoryRouter initialEntries={['/start']}>
```
**IonRedirect Removed**
The `IonRedirect` component has been removed. Use React Router's `<Navigate>` component instead:
```diff
- import { IonRedirect } from '@ionic/react';
- <IonRedirect path="/old" to="/new" exact />
+ import { Navigate } from 'react-router-dom';
+ <Route path="/old" element={<Navigate to="/new" replace />} />
```
**Path Regex Constraints Removed**
React Router v6 no longer supports regex constraints in path parameters (e.g., `/:tab(sessions)`). Use literal paths instead:
```diff
- <Route path="/:tab(sessions)" component={SessionsPage} />
- <Route path="/:tab(sessions)/:id" component={SessionDetail} />
+ <Route path="/sessions" element={<SessionsPage />} />
+ <Route path="/sessions/:id" element={<SessionDetail />} />
```
**IonRoute API Changes**
The `IonRoute` component follows the same API changes as React Router's `<Route>`. The `render` prop has been replaced with `element`, and the `exact` prop has been removed:
```diff
- <IonRoute path="/foo" exact render={(props) => <Foo {...props} />} />
+ <IonRoute path="/foo" element={<Foo />} />
```
For more information on migrating from React Router v5 to v6, refer to the [React Router v6 Upgrade Guide](https://reactrouter.com/6.28.0/upgrading/v5).

View File

@@ -138,7 +138,7 @@ export const IonRouter = ({ children, registerHistoryListener }: PropsWithChildr
let leavingLocationInfo: RouteInfo;
/**
* A programmatic navigation was triggered.
* e.g., `<Redirect />`, `history.push()`, or `handleNavigate()`
* e.g., `<Navigate />`, `navigate()`, or `handleNavigate()`
*/
if (incomingRouteParams.current) {
/**
@@ -176,7 +176,7 @@ export const IonRouter = ({ children, registerHistoryListener }: PropsWithChildr
/**
* A `REPLACE` action can be triggered by React Router's
* `<Redirect />` component.
* `<Navigate />` component.
*/
if (action === 'REPLACE') {
incomingRouteParams.current = {
@@ -280,7 +280,7 @@ export const IonRouter = ({ children, registerHistoryListener }: PropsWithChildr
} else {
routeInfo.pushedByRoute = lastRoute?.pushedByRoute ?? leavingLocationInfo.pathname;
}
// Triggered by `history.replace()` or a `<Redirect />` component, etc.
// Triggered by `navigate()` with replace or a `<Navigate />` component, etc.
} else if (routeInfo.routeAction === 'replace') {
/**
* Make sure to set the `lastPathname`, etc.. to the current route
@@ -566,7 +566,6 @@ export const IonRouter = ({ children, registerHistoryListener }: PropsWithChildr
<RouteManagerContext.Provider value={routeMangerContextValue}>
<NavManager
ionRoute={IonRouteInner}
ionRedirect={{}}
stackManager={StackManager}
routeInfo={routeInfo}
onNativeBack={handleNativeBack}

View File

@@ -1,34 +0,0 @@
import React from 'react';
import { NavContext } from '../contexts/NavContext';
export interface IonRedirectProps {
path?: string;
exact?: boolean;
to: string;
routerOptions?: unknown;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface IonRedirectState {}
export class IonRedirect extends React.PureComponent<IonRedirectProps, IonRedirectState> {
context!: React.ContextType<typeof NavContext>;
render() {
const IonRedirectInner = this.context.getIonRedirect();
if (!this.context.hasIonicRouter() || !IonRedirect) {
console.error(
'You either do not have an Ionic Router package, or your router does not support using <IonRedirect>'
);
return null;
}
return <IonRedirectInner {...this.props} />;
}
static get contextType() {
return NavContext;
}
}

View File

@@ -124,7 +124,7 @@ export { IonBackButton } from './navigation/IonBackButton';
export { IonRouterOutlet } from './IonRouterOutlet';
export { IonIcon } from './IonIcon';
export * from './IonRoute';
export * from './IonRedirect';
export * from './IonRouterContext';
// Utils

View File

@@ -7,8 +7,6 @@ import type { RouteInfo } from '../models';
export interface NavContextState {
getIonRoute: () => any;
getIonRedirect: () => any;
getPageManager: () => any;
getStackManager: () => any;
goBack: (route?: string | RouteInfo, animationBuilder?: AnimationBuilder) => void;
navigate: (
@@ -27,9 +25,7 @@ export interface NavContextState {
}
export const NavContext = /*@__PURE__*/ React.createContext<NavContextState>({
getIonRedirect: () => undefined,
getIonRoute: () => undefined,
getPageManager: () => undefined,
getStackManager: () => undefined,
goBack: (route?: string | RouteInfo) => {
if (typeof window !== 'undefined') {

View File

@@ -11,7 +11,6 @@ import type { RouterDirection } from '../models/RouterDirection';
import type { RouterOptions } from '../models/RouterOptions';
import type { LocationHistory } from './LocationHistory';
import PageManager from './PageManager';
// TODO(FW-2959): types
@@ -30,7 +29,6 @@ interface NavManagerProps {
onSetCurrentTab: (tab: string, routeInfo: RouteInfo) => void;
onChangeTab: (tab: string, path: string, routeOptions?: any) => void;
onResetTab: (tab: string, path: string, routeOptions?: any) => void;
ionRedirect: any;
ionRoute: any;
stackManager: any;
locationHistory: LocationHistory;
@@ -61,10 +59,8 @@ export class NavManager extends React.PureComponent<NavManagerProps, NavContextS
goBack: this.goBack.bind(this),
hasIonicRouter: () => true,
navigate: this.navigate.bind(this),
getIonRedirect: this.getIonRedirect.bind(this),
getIonRoute: this.getIonRoute.bind(this),
getStackManager: this.getStackManager.bind(this),
getPageManager: this.getPageManager.bind(this),
routeInfo: this.props.routeInfo,
setCurrentTab: this.props.onSetCurrentTab,
changeTab: this.props.onChangeTab,
@@ -111,14 +107,6 @@ export class NavManager extends React.PureComponent<NavManagerProps, NavContextS
this.props.onNavigate(path, action, direction, animationBuilder, options, tab);
}
getPageManager() {
return PageManager;
}
getIonRedirect() {
return this.props.ionRedirect;
}
getIonRoute() {
return this.props.ionRoute;
}