Skip to content

Commit 3878bf7

Browse files
authored
fix(react): outlet will not clear in react 18 with hot reload (#25703)
Resolves #25507
1 parent 3cd48ac commit 3878bf7

File tree

2 files changed

+44
-16
lines changed

2 files changed

+44
-16
lines changed

packages/react-router/src/ReactRouter/StackManager.tsx

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ interface StackManagerProps {
1818

1919
interface StackManagerState {}
2020

21-
const isViewVisible = (el: HTMLElement) => !el.classList.contains('ion-page-invisible') && !el.classList.contains('ion-page-hidden');
21+
const isViewVisible = (el: HTMLElement) =>
22+
!el.classList.contains('ion-page-invisible') && !el.classList.contains('ion-page-hidden');
2223

2324
export class StackManager extends React.PureComponent<StackManagerProps, StackManagerState> {
2425
id: string;
@@ -33,6 +34,7 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
3334
isInOutlet: () => true,
3435
};
3536

37+
private clearOutletTimeout: any;
3638
private pendingPageTransition = false;
3739

3840
constructor(props: StackManagerProps) {
@@ -46,9 +48,20 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
4648
}
4749

4850
componentDidMount() {
51+
if (this.clearOutletTimeout) {
52+
/**
53+
* The clearOutlet integration with React Router is a bit hacky.
54+
* It uses a timeout to clear the outlet after a transition.
55+
* In React v18, components are mounted and unmounted in development mode
56+
* to check for side effects.
57+
*
58+
* This clearTimeout prevents the outlet from being cleared when the component is re-mounted,
59+
* which should only happen in development mode and as a result of a hot reload.
60+
*/
61+
clearTimeout(this.clearOutletTimeout);
62+
}
4963
if (this.routerOutletElement) {
5064
this.setupRouterOutlet(this.routerOutletElement);
51-
// console.log(`SM Mount - ${this.routerOutletElement.id} (${this.id})`);
5265
this.handlePageTransition(this.props.routeInfo);
5366
}
5467
}
@@ -67,8 +80,7 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
6780
}
6881

6982
componentWillUnmount() {
70-
// console.log(`SM UNMount - ${(this.routerOutletElement?.id as any).id} (${this.id})`);
71-
this.context.clearOutlet(this.id);
83+
this.clearOutletTimeout = this.context.clearOutlet(this.id);
7284
}
7385

7486
async handlePageTransition(routeInfo: RouteInfo) {
@@ -142,13 +154,20 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
142154
* to find the leaving view item to transition between.
143155
*/
144156
if (!leavingViewItem && this.props.routeInfo.prevRouteLastPathname) {
145-
leavingViewItem = this.context.findViewItemByPathname(this.props.routeInfo.prevRouteLastPathname, this.id);
157+
leavingViewItem = this.context.findViewItemByPathname(
158+
this.props.routeInfo.prevRouteLastPathname,
159+
this.id
160+
);
146161
}
147162

148163
/**
149164
* If the entering view is already visible and the leaving view is not, the transition does not need to occur.
150165
*/
151-
if (isViewVisible(enteringViewItem.ionPageElement) && leavingViewItem !== undefined && !isViewVisible(leavingViewItem.ionPageElement!)) {
166+
if (
167+
isViewVisible(enteringViewItem.ionPageElement) &&
168+
leavingViewItem !== undefined &&
169+
!isViewVisible(leavingViewItem.ionPageElement!)
170+
) {
152171
return;
153172
}
154173

@@ -197,11 +216,16 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
197216
const canStart = () => {
198217
const config = getConfig();
199218
const swipeEnabled = config && config.get('swipeBackEnabled', routerOutlet.mode === 'ios');
200-
if (!swipeEnabled) { return false; }
219+
if (!swipeEnabled) {
220+
return false;
221+
}
201222

202223
const { routeInfo } = this.props;
203224

204-
const propsToUse = (this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute) ? this.prevProps.routeInfo : { pathname: routeInfo.pushedByRoute || '' } as any;
225+
const propsToUse =
226+
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
227+
? this.prevProps.routeInfo
228+
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
205229
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
206230

207231
return (
@@ -213,7 +237,6 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
213237
* root url.
214238
*/
215239
enteringViewItem.mount &&
216-
217240
/**
218241
* When on the first page (whatever view
219242
* you land on after the root url) it
@@ -229,7 +252,10 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
229252
const onStart = async () => {
230253
const { routeInfo } = this.props;
231254

232-
const propsToUse = (this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute) ? this.prevProps.routeInfo : { pathname: routeInfo.pushedByRoute || '' } as any;
255+
const propsToUse =
256+
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
257+
? this.prevProps.routeInfo
258+
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
233259
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
234260
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
235261

@@ -257,7 +283,10 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
257283
*/
258284
const { routeInfo } = this.props;
259285

260-
const propsToUse = (this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute) ? this.prevProps.routeInfo : { pathname: routeInfo.pushedByRoute || '' } as any;
286+
const propsToUse =
287+
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
288+
? this.prevProps.routeInfo
289+
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
261290
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
262291
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
263292

@@ -279,12 +308,12 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
279308
ionPageElement.classList.add('ion-page-hidden');
280309
}
281310
}
282-
}
311+
};
283312

284313
routerOutlet.swipeHandler = {
285314
canStart,
286315
onStart,
287-
onEnd
316+
onEnd,
288317
};
289318
}
290319

@@ -333,7 +362,7 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
333362
progressAnimation,
334363
animationBuilder: routeInfo.routeAnimation,
335364
});
336-
}
365+
};
337366

338367
const routerOutlet = this.routerOutletElement!;
339368

packages/react/src/routing/ViewStacks.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ export abstract class ViewStacks {
2323

2424
clear(outletId: string) {
2525
// Give some time for the leaving views to transition before removing
26-
setTimeout(() => {
27-
// console.log('Removing viewstack for outletID ' + outletId);
26+
return setTimeout(() => {
2827
delete this.viewStacks[outletId];
2928
}, 500);
3029
}

0 commit comments

Comments
 (0)