From aaf9d24afd79b071f2b919eb50ba034afd6c3bc4 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Fri, 18 Oct 2019 08:56:53 -0400 Subject: [PATCH 1/9] fix(menu): clamp out of bounds swipe value (#19684) fixes #18927 --- core/src/utils/animation/test/animation.spec.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/src/utils/animation/test/animation.spec.ts b/core/src/utils/animation/test/animation.spec.ts index 21c885af55..27b408b939 100644 --- a/core/src/utils/animation/test/animation.spec.ts +++ b/core/src/utils/animation/test/animation.spec.ts @@ -397,6 +397,18 @@ describe('cubic-bezier conversion', () => { expect(getTimeGivenProgression(...equation, 1.32)).toEqual([]); expect(getTimeGivenProgression(...equation, -0.32)).toEqual([]); }) + + it('cubic-bezier(0.32, 0.72, 0, 1) (with out of bounds progression)', () => { + const equation = [ + new Point(0, 0), + new Point(0.05, 0.2), + new Point(.14, 1.72), + new Point(1, 1) + ]; + + expect(getTimeGivenProgression(...equation, 1.32)).toBeNaN(); + expect(getTimeGivenProgression(...equation, -0.32)).toBeNaN(); + }) }) }); From 93bd4afb1d535dba61de16bea60e896d34f3bb60 Mon Sep 17 00:00:00 2001 From: Ely Lucas Date: Mon, 25 Nov 2019 11:15:03 -0700 Subject: [PATCH 2/9] chore(react): fix tabs docs (#19995) --- core/src/components/tabs/readme.md | 58 ++++++++++++++++-------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/core/src/components/tabs/readme.md b/core/src/components/tabs/readme.md index 48a37bd845..0949efcbae 100644 --- a/core/src/components/tabs/readme.md +++ b/core/src/components/tabs/readme.md @@ -176,34 +176,38 @@ will match the following tab: ### React ```tsx -import React from 'react'; -import { IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel, IonBadge } from '@ionic/react'; - export const TabsExample: React.FC = () => ( - - - - - Schedule - 6 - - - - - Speakers - - - - - Map - - - - - About - - - + + + + {/* + Using the render method prop cuts down the number of renders your components will have due to route changes. + Use the component prop when your component depends on the RouterComponentProps passed in automatically. + */} + } exact={true} /> + } exact={true} /> + } exact={true} /> + } exact={true} /> + + + + + Schedule + + + + Speakers + + + + Map + + + + About + + + ); ``` From ab0f92e01f892c1c5f0db6e1e8467fe553e48bdd Mon Sep 17 00:00:00 2001 From: Ely Lucas Date: Wed, 27 Nov 2019 16:08:56 -0700 Subject: [PATCH 3/9] fix(react): fix refs for controllers, overlays, ionpage, and ionrouteroutlet, fixes #19924 (#20012) --- .../src/ReactRouter/StackManager.tsx | 6 +++- packages/react/src/components/IonPage.tsx | 27 +++++++++++---- .../react/src/components/IonRouterOutlet.tsx | 2 +- .../react/src/components/IonicReactProps.ts | 1 + .../components/createControllerComponent.tsx | 27 ++++++++++++--- .../src/components/createOverlayComponent.tsx | 33 +++++++++++++++---- 6 files changed, 78 insertions(+), 18 deletions(-) diff --git a/packages/react-router/src/ReactRouter/StackManager.tsx b/packages/react-router/src/ReactRouter/StackManager.tsx index 6cccf00fbd..f9d85c3168 100644 --- a/packages/react-router/src/ReactRouter/StackManager.tsx +++ b/packages/react-router/src/ReactRouter/StackManager.tsx @@ -87,6 +87,10 @@ export class StackManager extends React.Component class IonPageInternal extends React.Component & IonicReactProps> { +interface IonPageProps extends IonicReactProps { +} + +interface IonPageInternalProps extends IonPageProps { + forwardedRef?: React.RefObject; +} + +class IonPageInternal extends React.Component { context!: React.ContextType; - ref = React.createRef(); + ref: React.RefObject;// React.createRef(); + + constructor(props: IonPageInternalProps) { + super(props); + this.ref = this.props.forwardedRef || React.createRef() + } componentDidMount() { - if (this.context && this.ref.current) { + if (this.context && this.ref && this.ref.current) { if (this.context.hasIonicRouter()) { this.context.registerIonPage(this.ref.current); } - } + } } render() { - const { className, children, ...props } = this.props; + const { className, children, forwardedRef, ...props } = this.props; return (
@@ -33,4 +46,6 @@ export const IonPage = /*@__PURE__*/(() => class IonPageInternal extends React.C static get contextType() { return NavContext; } -})(); +}; + +export const IonPage = createForwardRef(IonPageInternal, 'IonPage'); diff --git a/packages/react/src/components/IonRouterOutlet.tsx b/packages/react/src/components/IonRouterOutlet.tsx index 0c31bf3cc0..63d4a2e44d 100644 --- a/packages/react/src/components/IonRouterOutlet.tsx +++ b/packages/react/src/components/IonRouterOutlet.tsx @@ -13,7 +13,7 @@ type Props = LocalJSX.IonRouterOutlet & { }; type InternalProps = Props & { - forwardedRef: any; + forwardedRef?: React.RefObject; }; const IonRouterOutletContainer = /*@__PURE__*/(() => class extends React.Component { diff --git a/packages/react/src/components/IonicReactProps.ts b/packages/react/src/components/IonicReactProps.ts index a18e0b7f16..2ea45b08b3 100644 --- a/packages/react/src/components/IonicReactProps.ts +++ b/packages/react/src/components/IonicReactProps.ts @@ -1,5 +1,6 @@ export interface IonicReactProps { class?: string; + className?: string; style?: {[key: string]: any }; } diff --git a/packages/react/src/components/createControllerComponent.tsx b/packages/react/src/components/createControllerComponent.tsx index 62def561c7..ef8dcec68e 100644 --- a/packages/react/src/components/createControllerComponent.tsx +++ b/packages/react/src/components/createControllerComponent.tsx @@ -19,14 +19,17 @@ export const createControllerComponent = { const dismissEventName = `on${displayName}DidDismiss`; - type Props = OptionsType & ReactControllerProps; + type Props = OptionsType & ReactControllerProps & { + forwardedRef?: React.RefObject + }; - return class extends React.Component { + class Overlay extends React.Component { overlay?: OverlayType; isUnmounted = false; constructor(props: Props) { super(props); + this.handleDismiss = this.handleDismiss.bind(this); } static get displayName() { @@ -54,18 +57,30 @@ export const createControllerComponent = >) { + if (this.props.onDidDismiss) { + this.props.onDidDismiss(event); + } + if (this.props.forwardedRef) { + (this.props.forwardedRef as any).current = undefined; + } + } + async present(prevProps?: Props) { const { isOpen, onDidDismiss, ...cProps } = this.props; this.overlay = await controller.create({ ...cProps as any }); attachProps(this.overlay, { - [dismissEventName]: onDidDismiss + [dismissEventName]: this.handleDismiss }, prevProps); // Check isOpen again since the value could have changed during the async call to controller.create // It's also possible for the component to have become unmounted. if (this.props.isOpen === true && this.isUnmounted === false) { - await this.overlay.present(); + if (this.props.forwardedRef) { + (this.props.forwardedRef as any).current = this.overlay; + } + await this.overlay.present(); } } @@ -73,4 +88,8 @@ export const createControllerComponent = ((props, ref) => { + return + }) }; diff --git a/packages/react/src/components/createOverlayComponent.tsx b/packages/react/src/components/createOverlayComponent.tsx index 113736e2cf..7560904afd 100644 --- a/packages/react/src/components/createOverlayComponent.tsx +++ b/packages/react/src/components/createOverlayComponent.tsx @@ -15,21 +15,24 @@ export interface ReactOverlayProps { onDidDismiss?: (event: CustomEvent) => void; } -export const createOverlayComponent = ( +export const createOverlayComponent = ( displayName: string, controller: { create: (options: any) => Promise } ) => { const dismissEventName = `on${displayName}DidDismiss`; - type Props = T & ReactOverlayProps; + type Props = OverlayComponent & ReactOverlayProps & { + forwardedRef?: React.RefObject + }; - return class extends React.Component { + class Overlay extends React.Component { overlay?: OverlayType; el: HTMLDivElement; constructor(props: Props) { super(props); this.el = document.createElement('div'); + this.handleDismiss = this.handleDismiss.bind(this); } static get displayName() { @@ -46,6 +49,15 @@ export const createOverlayComponent = >) { + if (this.props.onDidDismiss) { + this.props.onDidDismiss(event); + } + if (this.props.forwardedRef) { + (this.props.forwardedRef as any).current = undefined; + } + } + async componentDidUpdate(prevProps: Props) { if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === true) { this.present(prevProps); @@ -56,10 +68,11 @@ export const createOverlayComponent = { return; }, ...cProps } = this.props; + const { children, isOpen, onDidDismiss, ...cProps } = this.props; const elementProps = { ...cProps, - [dismissEventName]: onDidDismiss + ref: this.props.forwardedRef, + [dismissEventName]: this.handleDismiss }; const overlay = this.overlay = await controller.create({ @@ -68,6 +81,10 @@ export const createOverlayComponent = ((props, ref) => { + return + }) }; From 693ae21096939ca4a75103494f0c33153bf06153 Mon Sep 17 00:00:00 2001 From: Ely Lucas Date: Tue, 3 Dec 2019 14:22:26 -0700 Subject: [PATCH 4/9] fix(react): support navigating to same page and route updates in IonRouterOutlet, fixes #19891, #19892, #19986 --- .../src/ReactRouter/NavManager.tsx | 10 +- .../src/ReactRouter/RouteManagerContext.ts | 6 +- .../react-router/src/ReactRouter/Router.tsx | 167 +++++++++++++----- .../src/ReactRouter/StackManager.tsx | 47 +++-- .../react-router/src/ReactRouter/View.tsx | 9 +- .../react-router/src/ReactRouter/ViewItem.ts | 9 +- .../src/ReactRouter/ViewStacks.ts | 7 +- .../src/components/navigation/IonTabBar.tsx | 11 +- packages/react/src/contexts/NavContext.ts | 4 +- 9 files changed, 180 insertions(+), 90 deletions(-) diff --git a/packages/react-router/src/ReactRouter/NavManager.tsx b/packages/react-router/src/ReactRouter/NavManager.tsx index a01bc7873b..e73cd756eb 100644 --- a/packages/react-router/src/ReactRouter/NavManager.tsx +++ b/packages/react-router/src/ReactRouter/NavManager.tsx @@ -3,7 +3,6 @@ import { NavContext, NavContextState } from '@ionic/react'; import { Location as HistoryLocation, UnregisterCallback } from 'history'; import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; - import { StackManager } from './StackManager'; interface NavManagerProps extends RouteComponentProps { @@ -25,7 +24,6 @@ export class NavManager extends React.Component { return; }, // overridden in View for each IonPage - tabNavigate: this.tabNavigate.bind(this) }; this.listenUnregisterCallback = this.props.history.listen((location: HistoryLocation) => { @@ -53,12 +51,8 @@ export class NavManager extends React.Component void; + syncRoute: (id: string, route: any) => void; hideView: (viewId: string) => void; viewStacks: ViewStacks; setupIonRouter: (id: string, children: ReactNode, routerOutlet: HTMLIonRouterOutletElement) => void; removeViewStack: (stack: string) => void; + getRoute: (id: string) => any; } export const RouteManagerContext = /*@__PURE__*/React.createContext({ viewStacks: new ViewStacks(), syncView: () => { navContextNotFoundError(); }, + syncRoute: () => { navContextNotFoundError(); }, hideView: () => { navContextNotFoundError(); }, setupIonRouter: () => Promise.reject(navContextNotFoundError()), - removeViewStack: () => { navContextNotFoundError(); } + removeViewStack: () => { navContextNotFoundError(); }, + getRoute: () => { navContextNotFoundError(); } }); function navContextNotFoundError() { diff --git a/packages/react-router/src/ReactRouter/Router.tsx b/packages/react-router/src/ReactRouter/Router.tsx index 2da67dc729..50bfdb5f61 100644 --- a/packages/react-router/src/ReactRouter/Router.tsx +++ b/packages/react-router/src/ReactRouter/Router.tsx @@ -2,7 +2,7 @@ import { NavDirection } from '@ionic/core'; import { RouterDirection, getConfig } from '@ionic/react'; import { Action as HistoryAction, Location as HistoryLocation, UnregisterCallback } from 'history'; import React from 'react'; -import { RouteComponentProps, matchPath, withRouter } from 'react-router-dom'; +import { RouteComponentProps, withRouter, matchPath } from 'react-router-dom'; import { generateId, isDevMode } from '../utils'; import { LocationHistory } from '../utils/LocationHistory'; @@ -23,6 +23,7 @@ class RouteManager extends React.Component { const { view: enteringView, viewStack: enteringViewStack, match } = viewStacks.findViewInfoByLocation(location, key); if (!enteringView || !enteringViewStack) { return; } + leavingView = viewStacks.findViewInfoById(this.activeIonPageId).view; if (enteringView.isIonRoute) { enteringView.show = true; enteringView.mount = true; enteringView.routeData.match = match!; + shouldTransitionPage = true; this.activeIonPageId = enteringView.id; @@ -129,10 +144,12 @@ class RouteManager extends React.Component { - const { view: enteringView, viewStack } = this.state.viewStacks.findViewInfoById(this.activeIonPageId); - if (enteringView && viewStack) { - const enteringEl = enteringView.ionPageElement ? enteringView.ionPageElement : undefined; - const leavingEl = leavingView && leavingView.ionPageElement ? leavingView.ionPageElement : undefined; - if (enteringEl) { - // Don't animate from an empty view - const navDirection = leavingEl && leavingEl.innerHTML === '' ? undefined : direction === 'none' ? undefined : direction; - const shouldGoBack = !!enteringView.prevId || !!this.locationHistory.previous(); - this.commitView( - enteringEl!, - leavingEl!, - viewStack.routerOutlet, - navDirection, - shouldGoBack); - } else if (leavingEl) { - leavingEl.classList.add('ion-page-hidden'); - leavingEl.setAttribute('aria-hidden', 'true'); + if (shouldTransitionPage) { + const { view: enteringView, viewStack } = this.state.viewStacks.findViewInfoById(this.activeIonPageId); + if (enteringView && viewStack) { + const enteringEl = enteringView.ionPageElement ? enteringView.ionPageElement : undefined; + const leavingEl = leavingView && leavingView.ionPageElement ? leavingView.ionPageElement : undefined; + if (enteringEl) { + // Don't animate from an empty view + const navDirection = leavingEl && leavingEl.innerHTML === '' ? undefined : direction === 'none' ? undefined : direction; + const shouldGoBack = !!enteringView.prevId || !!this.locationHistory.previous(); + this.commitView( + enteringEl!, + leavingEl!, + viewStack.routerOutlet, + navDirection, + shouldGoBack, + leavingViewHtml); + } else if (leavingEl) { + leavingEl.classList.add('ion-page-hidden'); + leavingEl.setAttribute('aria-hidden', 'true'); + } } // Warn if an IonPage was not eventually rendered in Dev Mode if (isDevMode()) { - if (enteringView.routeData.match!.url !== location.pathname) { + if (enteringView && enteringView.routeData.match!.url !== location.pathname) { setTimeout(() => { const { view } = this.state.viewStacks.findViewInfoById(this.activeIonPageId); if (view!.routeData.match!.url !== location.pathname) { @@ -212,15 +232,18 @@ class RouteManager extends React.Component { - views.push(createViewItem(child, this.props.history.location)); + const routeId = generateId(); + this.routes[routeId] = child; + views.push(createViewItem(child, routeId, this.props.history.location)); }); this.registerViewStack(id, activeId, views, routerOutlet, this.props.location); - function createViewItem(child: React.ReactElement, location: HistoryLocation) { + function createViewItem(child: React.ReactElement, routeId: string, location: HistoryLocation) { const viewId = generateId(); const key = generateId(); - const route = child; + + // const route = child; const matchProps = { exact: child.props.exact, path: child.props.path || child.props.from, @@ -234,7 +257,7 @@ class RouteManager extends React.Component { + for (let routeKey in this.routes) { + const route = this.routes[routeKey]; + if (route.props.path == child.props.path) { + this.routes[routeKey] = child; + } + } }); + } + + private async commitView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOutlet: HTMLIonRouterOutletElement, direction?: NavDirection, showGoBack?: boolean, leavingViewHtml?: string) { + + if ((enteringEl === leavingEl) && direction && leavingViewHtml) { + // If a page is transitioning to another version of itself + // we clone it so we can have an animation to show + const newLeavingElement = clonePageElement(leavingViewHtml); + ionRouterOutlet.appendChild(newLeavingElement); + await ionRouterOutlet.commit(enteringEl, newLeavingElement, { + deepWait: true, + duration: direction === undefined ? 0 : undefined, + direction, + showGoBack, + progressAnimation: false + }); + ionRouterOutlet.removeChild(newLeavingElement); + } else { + await ionRouterOutlet.commit(enteringEl, leavingEl, { + deepWait: true, + duration: direction === undefined ? 0 : undefined, + direction, + showGoBack, + progressAnimation: false + }); + } if (leavingEl && (enteringEl !== leavingEl)) { /** add hidden attributes */ @@ -357,23 +404,32 @@ class RouteManager extends React.Component { +class StackManagerInner extends React.Component { routerOutletEl: React.RefObject = React.createRef(); - context!: React.ContextType; + id: string; constructor(props: StackManagerProps) { @@ -31,7 +33,7 @@ export class StackManager extends React.Component { this.setState({ routerOutletReady: true @@ -39,33 +41,39 @@ export class StackManager extends React.Component x.show); const ionRouterOutlet = React.Children.only(this.props.children) as React.ReactElement; const { routerOutletReady } = this.state; const childElements = routerOutletReady ? views.map(view => { + const route = routeManager.getRoute(view.routeId); return ( - {this.renderChild(view)} + {this.renderChild(view, route)} ); @@ -99,8 +108,14 @@ export class StackManager extends React.Component { + return (props: any) => ( + + {context => } + + ) +} + +export const StackManager = withContext(StackManagerInner); diff --git a/packages/react-router/src/ReactRouter/View.tsx b/packages/react-router/src/ReactRouter/View.tsx index 95043e0858..9ce1561b1d 100644 --- a/packages/react-router/src/ReactRouter/View.tsx +++ b/packages/react-router/src/ReactRouter/View.tsx @@ -10,6 +10,7 @@ interface ViewProps extends React.HTMLAttributes { onViewSync: (page: HTMLElement, viewId: string) => void; onHideView: (viewId: string) => void; view: ViewItem; + route: any; } /** @@ -23,11 +24,11 @@ export class View extends React.Component { /** * If we can tell if view is a redirect, hide it so it will work again in future */ - const { view } = this.props; - if (view.route.type === Redirect) { + const { view, route } = this.props; + if (route.type === Redirect) { this.props.onHideView(view.id); - } else if (view.route.type === Route && view.route.props.render) { - if (view.route.props.render().type === Redirect) { + } else if (route.type === Route && route.props.render) { + if (route.props.render().type === Redirect) { this.props.onHideView(view.id); } } diff --git a/packages/react-router/src/ReactRouter/ViewItem.ts b/packages/react-router/src/ReactRouter/ViewItem.ts index 22b7e597e9..f9bc4932a6 100644 --- a/packages/react-router/src/ReactRouter/ViewItem.ts +++ b/packages/react-router/src/ReactRouter/ViewItem.ts @@ -3,8 +3,10 @@ export interface ViewItem { id: string; /** The key used by React. A new key is generated each time the view comes into the DOM so React thinks its a completely new element. */ key: string; + + routeId: string; /** The or component associated with the view */ - route: React.ReactElement; + // route: React.ReactElement; /** The reference to the element. */ ionPageElement?: HTMLElement; /** The routeData for the view. */ @@ -23,4 +25,9 @@ export interface ViewItem { * An IonRoute is a Route that contains an IonPage. Only IonPages participate in transition and lifecycle events. */ isIonRoute: boolean; + + /** + * location of the view + */ + location?: string; } diff --git a/packages/react-router/src/ReactRouter/ViewStacks.ts b/packages/react-router/src/ReactRouter/ViewStacks.ts index 660baa87c5..dd37a895cb 100644 --- a/packages/react-router/src/ReactRouter/ViewStacks.ts +++ b/packages/react-router/src/ReactRouter/ViewStacks.ts @@ -58,10 +58,11 @@ export class ViewStacks { path: v.routeData.childProps.path || v.routeData.childProps.from, component: v.routeData.childProps.component }; - match = matchPath(location.pathname, matchProps); - if (match) { + const myMatch: IonRouteData['match'] | null | undefined = matchPath(location.pathname, matchProps); + if (myMatch) { view = v; - return true; + match = myMatch; + return view.location === location.pathname; } return false; } diff --git a/packages/react/src/components/navigation/IonTabBar.tsx b/packages/react/src/components/navigation/IonTabBar.tsx index fb656acfd0..c22c32257d 100644 --- a/packages/react/src/components/navigation/IonTabBar.tsx +++ b/packages/react/src/components/navigation/IonTabBar.tsx @@ -69,12 +69,13 @@ const IonTabBarUnwrapped = /*@__PURE__*/(() => class extends React.Component) => { + const originalHref = this.state.tabs[e.detail.tab].originalHref; + const currentHref = this.state.tabs[e.detail.tab].currentHref; if (this.state.activeTab === e.detail.tab) { - const originalHref = this.state.tabs[e.detail.tab].originalHref; - if (this.context.hasIonicRouter()) { - this.context.tabNavigate(originalHref); + if (originalHref === currentHref) { + this.context.navigate(originalHref, 'none'); } else { - this.context.navigate(originalHref, 'back'); + this.context.navigate(originalHref, 'back', 'replace'); } } else { if (this.props.onIonTabsWillChange) { @@ -83,7 +84,7 @@ const IonTabBarUnwrapped = /*@__PURE__*/(() => class extends React.Component any; getStackManager: () => any; goBack: (defaultHref?: string) => void; - navigate: (path: string, direction?: RouterDirection | 'none') => void; + navigate: (path: string, direction?: RouterDirection | 'none', type?: 'push' | 'replace') => void; hasIonicRouter: () => boolean; registerIonPage: (page: HTMLElement) => void; - tabNavigate: (url: string) => void; currentPath: string | undefined; } @@ -23,7 +22,6 @@ export const NavContext = /*@__PURE__*/React.createContext({ } }, navigate: (path: string) => { window.location.pathname = path; }, - tabNavigate: () => undefined, hasIonicRouter: () => false, registerIonPage: () => undefined, currentPath: undefined From b8517781b14ae44abdde94f1d3f64296a64bf310 Mon Sep 17 00:00:00 2001 From: Ely Lucas Date: Tue, 3 Dec 2019 15:29:55 -0700 Subject: [PATCH 5/9] fix(react): don't show back button when not appropriate --- packages/react-router/package.json | 4 ++-- .../src/ReactRouter/NavManager.tsx | 1 + .../react-router/src/ReactRouter/Router.tsx | 24 ++++++++----------- .../src/ReactRouter/StackManager.tsx | 6 ++--- packages/react-router/tslint.json | 5 +++- packages/react/package.json | 4 ++-- packages/react/src/components/IonPage.tsx | 8 +++---- .../components/createControllerComponent.tsx | 12 +++++----- .../src/components/createOverlayComponent.tsx | 10 ++++---- packages/react/tslint.json | 5 ++-- 10 files changed, 40 insertions(+), 39 deletions(-) diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 80894d8e61..ef7d269004 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -50,7 +50,7 @@ "@ionic/core": "5.0.0-beta.1", "@ionic/react": "5.0.0-beta.1", "@types/jest": "^23.3.9", - "@types/node": "12.6.9", + "@types/node": "^12.12.14", "@types/react": "^16.9.2", "@types/react-dom": "^16.9.0", "@types/react-router": "^5.0.3", @@ -70,7 +70,7 @@ "tslint": "^5.20.0", "tslint-ionic-rules": "0.0.21", "tslint-react": "^4.1.0", - "typescript": "3.5.3" + "typescript": "^3.7.2" }, "jest": { "preset": "ts-jest", diff --git a/packages/react-router/src/ReactRouter/NavManager.tsx b/packages/react-router/src/ReactRouter/NavManager.tsx index e73cd756eb..edcb646173 100644 --- a/packages/react-router/src/ReactRouter/NavManager.tsx +++ b/packages/react-router/src/ReactRouter/NavManager.tsx @@ -3,6 +3,7 @@ import { NavContext, NavContextState } from '@ionic/react'; import { Location as HistoryLocation, UnregisterCallback } from 'history'; import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; + import { StackManager } from './StackManager'; interface NavManagerProps extends RouteComponentProps { diff --git a/packages/react-router/src/ReactRouter/Router.tsx b/packages/react-router/src/ReactRouter/Router.tsx index 50bfdb5f61..499b92914b 100644 --- a/packages/react-router/src/ReactRouter/Router.tsx +++ b/packages/react-router/src/ReactRouter/Router.tsx @@ -2,7 +2,7 @@ import { NavDirection } from '@ionic/core'; import { RouterDirection, getConfig } from '@ionic/react'; import { Action as HistoryAction, Location as HistoryLocation, UnregisterCallback } from 'history'; import React from 'react'; -import { RouteComponentProps, withRouter, matchPath } from 'react-router-dom'; +import { RouteComponentProps, matchPath, withRouter } from 'react-router-dom'; import { generateId, isDevMode } from '../utils'; import { LocationHistory } from '../utils/LocationHistory'; @@ -23,7 +23,7 @@ class RouteManager extends React.Component { - for (let routeKey in this.routes) { + for (const routeKey in this.routes) { const route = this.routes[routeKey]; - if (route.props.path == child.props.path) { + if (route.props.path === child.props.path) { this.routes[routeKey] = child; } } @@ -408,11 +404,11 @@ class RouteManager extends React.Component { {context => } - ) -} + ); +}; export const StackManager = withContext(StackManagerInner); diff --git a/packages/react-router/tslint.json b/packages/react-router/tslint.json index 2f90574c64..d46400c97a 100644 --- a/packages/react-router/tslint.json +++ b/packages/react-router/tslint.json @@ -27,6 +27,9 @@ "jsx-no-bind": false, "jsx-no-lambda": false, "jsx-no-multiline-js": false, - "jsx-wrap-multiline": false + "jsx-wrap-multiline": false, + "forin": false, + "strict-type-predicates": false, + "no-unused-expression": false } } diff --git a/packages/react/package.json b/packages/react/package.json index 3ab1f0e3db..091b933ff3 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -49,7 +49,7 @@ }, "devDependencies": { "@types/jest": "^23.3.9", - "@types/node": "10.12.9", + "@types/node": "^12.12.14", "@types/react": "^16.9.2", "@types/react-dom": "^16.9.0", "fs-extra": "^8.1.0", @@ -67,7 +67,7 @@ "tslint": "^5.18.0", "tslint-ionic-rules": "0.0.21", "tslint-react": "^4.0.0", - "typescript": "3.5.3" + "typescript": "^3.7.2" }, "jest": { "preset": "ts-jest", diff --git a/packages/react/src/components/IonPage.tsx b/packages/react/src/components/IonPage.tsx index 1faa2e7b25..17ff6efe37 100644 --- a/packages/react/src/components/IonPage.tsx +++ b/packages/react/src/components/IonPage.tsx @@ -14,11 +14,11 @@ interface IonPageInternalProps extends IonPageProps { class IonPageInternal extends React.Component { context!: React.ContextType; - ref: React.RefObject;// React.createRef(); + ref: React.RefObject; constructor(props: IonPageInternalProps) { super(props); - this.ref = this.props.forwardedRef || React.createRef() + this.ref = this.props.forwardedRef || React.createRef(); } componentDidMount() { @@ -26,7 +26,7 @@ class IonPageInternal extends React.Component { if (this.context.hasIonicRouter()) { this.context.registerIonPage(this.ref.current); } - } + } } render() { @@ -46,6 +46,6 @@ class IonPageInternal extends React.Component { static get contextType() { return NavContext; } -}; +} export const IonPage = createForwardRef(IonPageInternal, 'IonPage'); diff --git a/packages/react/src/components/createControllerComponent.tsx b/packages/react/src/components/createControllerComponent.tsx index ef8dcec68e..b0bb79ee4e 100644 --- a/packages/react/src/components/createControllerComponent.tsx +++ b/packages/react/src/components/createControllerComponent.tsx @@ -15,12 +15,12 @@ export interface ReactControllerProps { export const createControllerComponent = ( displayName: string, - controller: { create: (options: OptionsType) => Promise } + controller: { create: (options: OptionsType) => Promise; } ) => { const dismissEventName = `on${displayName}DidDismiss`; type Props = OptionsType & ReactControllerProps & { - forwardedRef?: React.RefObject + forwardedRef?: React.RefObject; }; class Overlay extends React.Component { @@ -80,16 +80,16 @@ export const createControllerComponent = ((props, ref) => { - return - }) + return ; + }); }; diff --git a/packages/react/src/components/createOverlayComponent.tsx b/packages/react/src/components/createOverlayComponent.tsx index 7560904afd..63bd121dc1 100644 --- a/packages/react/src/components/createOverlayComponent.tsx +++ b/packages/react/src/components/createOverlayComponent.tsx @@ -17,12 +17,12 @@ export interface ReactOverlayProps { export const createOverlayComponent = ( displayName: string, - controller: { create: (options: any) => Promise } + controller: { create: (options: any) => Promise; } ) => { const dismissEventName = `on${displayName}DidDismiss`; type Props = OverlayComponent & ReactOverlayProps & { - forwardedRef?: React.RefObject + forwardedRef?: React.RefObject; }; class Overlay extends React.Component { @@ -96,9 +96,9 @@ export const createOverlayComponent = ((props, ref) => { - return - }) + return ; + }); }; diff --git a/packages/react/tslint.json b/packages/react/tslint.json index 3ba6e0b026..fb8fdaa478 100644 --- a/packages/react/tslint.json +++ b/packages/react/tslint.json @@ -14,7 +14,6 @@ "trailing-comma": false, "no-null-keyword": false, "no-console": false, - "no-unbound-method": true, "no-floating-promises": false, "no-invalid-template-strings": true, "ban-export-const-enum": true, @@ -27,6 +26,8 @@ "jsx-no-bind": false, "jsx-no-lambda": false, "jsx-no-multiline-js": false, - "jsx-wrap-multiline": false + "jsx-wrap-multiline": false, + "no-empty-interface": false, + "no-unbound-method": false } } From 43712db1be0f6559e01e199b6b79ddf50e130819 Mon Sep 17 00:00:00 2001 From: Ely Lucas Date: Mon, 9 Dec 2019 14:36:47 -0700 Subject: [PATCH 6/9] fix(react): first render performance improvements --- core/src/components/tabs/readme.md | 58 +++-- .../src/ReactRouter/IonRouteAction.ts | 1 + .../src/ReactRouter/NavManager.tsx | 9 +- .../react-router/src/ReactRouter/Router.tsx | 217 ++++++++++-------- .../src/ReactRouter/StackManager.tsx | 20 +- .../react-router/src/ReactRouter/View.tsx | 15 -- .../react-router/src/ReactRouter/ViewItem.ts | 5 +- .../src/ReactRouter/ViewStacks.ts | 1 - packages/react-router/tslint.json | 4 +- .../src/components/navigation/IonTabBar.tsx | 2 +- packages/react/src/contexts/NavContext.ts | 2 +- 11 files changed, 160 insertions(+), 174 deletions(-) create mode 100644 packages/react-router/src/ReactRouter/IonRouteAction.ts diff --git a/core/src/components/tabs/readme.md b/core/src/components/tabs/readme.md index 0949efcbae..48a37bd845 100644 --- a/core/src/components/tabs/readme.md +++ b/core/src/components/tabs/readme.md @@ -176,38 +176,34 @@ will match the following tab: ### React ```tsx +import React from 'react'; +import { IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel, IonBadge } from '@ionic/react'; + export const TabsExample: React.FC = () => ( - - - - {/* - Using the render method prop cuts down the number of renders your components will have due to route changes. - Use the component prop when your component depends on the RouterComponentProps passed in automatically. - */} - } exact={true} /> - } exact={true} /> - } exact={true} /> - } exact={true} /> - - - - - Schedule - - - - Speakers - - - - Map - - - - About - - - + + + + + Schedule + 6 + + + + + Speakers + + + + + Map + + + + + About + + + ); ``` diff --git a/packages/react-router/src/ReactRouter/IonRouteAction.ts b/packages/react-router/src/ReactRouter/IonRouteAction.ts new file mode 100644 index 0000000000..7e03063166 --- /dev/null +++ b/packages/react-router/src/ReactRouter/IonRouteAction.ts @@ -0,0 +1 @@ +export type IonRouteAction = 'push' | 'replace' | 'pop'; diff --git a/packages/react-router/src/ReactRouter/NavManager.tsx b/packages/react-router/src/ReactRouter/NavManager.tsx index edcb646173..23ac122330 100644 --- a/packages/react-router/src/ReactRouter/NavManager.tsx +++ b/packages/react-router/src/ReactRouter/NavManager.tsx @@ -4,11 +4,12 @@ import { Location as HistoryLocation, UnregisterCallback } from 'history'; import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; +import { IonRouteAction } from './IonRouteAction'; import { StackManager } from './StackManager'; interface NavManagerProps extends RouteComponentProps { onNavigateBack: (defaultHref?: string) => void; - onNavigate: (type: 'push' | 'replace', path: string, state?: any) => void; + onNavigate: (type: 'push' | 'replace' | 'pop', path: string, state?: any) => void; } export class NavManager extends React.Component { @@ -24,7 +25,7 @@ export class NavManager extends React.Component { return; }, // overridden in View for each IonPage + registerIonPage: () => { return; } // overridden in View for each IonPage }; this.listenUnregisterCallback = this.props.history.listen((location: HistoryLocation) => { @@ -52,8 +53,8 @@ export class NavManager extends React.Component { listenUnregisterCallback: UnregisterCallback | undefined; activeIonPageId?: string; - currentDirection?: RouterDirection; + currentIonRouteAction?: IonRouteAction; + currentRouteDirection?: RouterDirection; locationHistory = new LocationHistory(); - routes: { [key: string]: any; } = {}; + routes: { [key: string]: React.ReactElement; } = {}; + ionPageElements: { [key: string]: HTMLElement; } = {}; + routerOutlets: { [key: string]: HTMLIonRouterOutletElement; } = {}; + firstRender = true; constructor(props: RouteComponentProps) { super(props); @@ -52,7 +57,8 @@ class RouteManager extends React.Component { - if (routerOutlet.componentOnReady) { - routerOutlet.dispatchEvent(new Event('routerOutletReady')); - return; - } else { - setTimeout(() => { - waitUntilReady(); - }, 0); - } - }; - - await waitUntilReady(); const canStart = () => { const config = getConfig(); @@ -329,20 +324,13 @@ class RouteManager extends React.Component { - const viewStacks = Object.assign(new ViewStacks(), state.viewStacks); - const { view } = viewStacks.findViewInfoById(viewId); - - view!.ionPageElement = page; - view!.isIonRoute = true; - - return { - viewStacks - }; - - }, () => { - this.setActiveView(this.state.location || this.props.location, this.state.action!); - }); + const viewStacks = Object.assign(new ViewStacks(), this.state.viewStacks); + const { view } = viewStacks.findViewInfoById(viewId); + if (view) { + view.isIonRoute = true; + this.ionPageElements[view.id] = page; + this.setActiveView(this.state.location || this.props.location, this.state.action!, viewStacks); + } } syncRoute(_id: string, routerOutlet: any) { @@ -359,43 +347,62 @@ class RouteManager extends React.Component