mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Issue number: resolves #28838 --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> In #28316 we resolved a longstanding misconfiguration where event listeners being added to the page were not removed. This was due to incorrect usage of `.bind` creating a new instance of the callback functions. By removing the event listener for `ionViewDidLeave`, before the component has actually unmounted in react, resulted in the registered destroy callback to not fire:51c729eafc/packages/react/src/contexts/IonLifeCycleContext.tsx (L208-L216)and51c729eafc/packages/react/src/routing/ViewLifeCycleManager.tsx (L21-L32)This resulted in a scenario that using a `Redirect` could cause the wrong view to be unmounted (the entering view) and leave the user on an empty screen. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - `ionViewDidEnter` event listener *is not* removed while the component is unmounting. The browser will naturally remove the event listener when the element node is detached from the DOM. - Users are no longer presented with a white screen after clicking a route that uses a redirect. ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change: 1. Describe the impact and migration path for existing applications below. 2. Update the BREAKING.md file with the breaking change. 3. Add "BREAKING CHANGE: [...]" to the commit description when merging. See https://github.com/ionic-team/ionic-framework/blob/main/.github/CONTRIBUTING.md#footer for more information. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> Dev-build: `7.6.7-dev.11706567011.11e782a9`
106 lines
3.9 KiB
TypeScript
106 lines
3.9 KiB
TypeScript
import React from 'react';
|
|
|
|
import { mergeRefs } from '../components/react-component-lib/utils';
|
|
import { IonLifeCycleContext } from '../contexts/IonLifeCycleContext';
|
|
import type { RouteInfo } from '../models';
|
|
|
|
import { StackContext } from './StackContext';
|
|
|
|
interface PageManagerProps {
|
|
className?: string;
|
|
forwardedRef?: React.ForwardedRef<HTMLDivElement>;
|
|
routeInfo?: RouteInfo;
|
|
}
|
|
|
|
export class PageManager extends React.PureComponent<PageManagerProps> {
|
|
ionLifeCycleContext!: React.ContextType<typeof IonLifeCycleContext>;
|
|
context!: React.ContextType<typeof StackContext>;
|
|
ionPageElementRef: React.RefObject<HTMLDivElement>;
|
|
stableMergedRefs: React.RefCallback<HTMLDivElement>;
|
|
|
|
constructor(props: PageManagerProps) {
|
|
super(props);
|
|
this.ionPageElementRef = React.createRef();
|
|
// React refs must be stable (not created inline).
|
|
this.stableMergedRefs = mergeRefs(this.ionPageElementRef, this.props.forwardedRef);
|
|
|
|
/**
|
|
* This binds the scope of the following methods to the class scope.
|
|
* The `.bind` method returns a new function, so we need to assign it
|
|
* in the constructor rather than when adding or removing the listeners
|
|
* to avoid creating a new function.
|
|
*/
|
|
this.ionViewWillEnterHandler = this.ionViewWillEnterHandler.bind(this);
|
|
this.ionViewDidEnterHandler = this.ionViewDidEnterHandler.bind(this);
|
|
this.ionViewWillLeaveHandler = this.ionViewWillLeaveHandler.bind(this);
|
|
this.ionViewDidLeaveHandler = this.ionViewDidLeaveHandler.bind(this);
|
|
}
|
|
|
|
componentDidMount() {
|
|
if (this.ionPageElementRef.current) {
|
|
if (this.context.isInOutlet()) {
|
|
this.ionPageElementRef.current.classList.add('ion-page-invisible');
|
|
}
|
|
this.context.registerIonPage(this.ionPageElementRef.current, this.props.routeInfo!);
|
|
this.ionPageElementRef.current.addEventListener('ionViewWillEnter', this.ionViewWillEnterHandler);
|
|
this.ionPageElementRef.current.addEventListener('ionViewDidEnter', this.ionViewDidEnterHandler);
|
|
this.ionPageElementRef.current.addEventListener('ionViewWillLeave', this.ionViewWillLeaveHandler);
|
|
this.ionPageElementRef.current.addEventListener('ionViewDidLeave', this.ionViewDidLeaveHandler);
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
if (this.ionPageElementRef.current) {
|
|
this.ionPageElementRef.current.removeEventListener('ionViewWillEnter', this.ionViewWillEnterHandler);
|
|
this.ionPageElementRef.current.removeEventListener('ionViewDidEnter', this.ionViewDidEnterHandler);
|
|
this.ionPageElementRef.current.removeEventListener('ionViewWillLeave', this.ionViewWillLeaveHandler);
|
|
/**
|
|
* We deliberately do not remove the `ionViewDidLeave` listener.
|
|
* The registered callback is used to unmount and remove the page.
|
|
* Removing the event listener prevents the callback from being called.
|
|
* The browser will automatically remove the event listener when the
|
|
* page element is removed from the DOM and garbage collected.
|
|
*/
|
|
}
|
|
}
|
|
|
|
ionViewWillEnterHandler() {
|
|
this.ionLifeCycleContext.ionViewWillEnter();
|
|
}
|
|
|
|
ionViewDidEnterHandler() {
|
|
this.ionLifeCycleContext.ionViewDidEnter();
|
|
}
|
|
|
|
ionViewWillLeaveHandler() {
|
|
this.ionLifeCycleContext.ionViewWillLeave();
|
|
}
|
|
|
|
ionViewDidLeaveHandler() {
|
|
this.ionLifeCycleContext.ionViewDidLeave();
|
|
}
|
|
|
|
render() {
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const { className, children, routeInfo, forwardedRef, ...props } = this.props;
|
|
|
|
return (
|
|
<IonLifeCycleContext.Consumer>
|
|
{(context) => {
|
|
this.ionLifeCycleContext = context;
|
|
return (
|
|
<div className={className ? `${className} ion-page` : `ion-page`} ref={this.stableMergedRefs} {...props}>
|
|
{children}
|
|
</div>
|
|
);
|
|
}}
|
|
</IonLifeCycleContext.Consumer>
|
|
);
|
|
}
|
|
|
|
static get contextType() {
|
|
return StackContext;
|
|
}
|
|
}
|
|
export default PageManager;
|