Skip to content

Commit

Permalink
Use ReactDOM.createPortal instead of ReactDOM.render for nested views
Browse files Browse the repository at this point in the history
  • Loading branch information
Anber committed Oct 24, 2018
1 parent 03e8913 commit 5f76b16
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 23 deletions.
34 changes: 14 additions & 20 deletions src/angularjs/ReactUIViewAdapterComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import * as React from 'react';
import * as angular from 'angular';
import * as ReactDOM from 'react-dom';
import { hybridModule } from './module';
import { UIView, UIViewProps, UIRouterConsumer, UIViewConsumer } from '@uirouter/react';
import { filter } from '@uirouter/core';
import { UIRouterContextComponent } from '../react/UIRouterReactContext';
import ReactUIView from '../react/ReactUIView';

// When an angularjs `ui-view` is instantiated, also create an react-ui-view-adapter (which creates a react UIView)
hybridModule.directive('uiView', function() {
Expand Down Expand Up @@ -92,14 +90,24 @@ hybridModule.directive('reactUiViewAdapter', function() {
}

const props = { ...attrs, render, wrap: false, refFn: ref };
const setChildViewProps = (scope as any).setChildViewProps;
if (setChildViewProps) {
setChildViewProps(props, el);
} else {
ReactDOM.render<any>(<ReactUIView {...props} />, el as any);
}
// console.log(`${$id}: rendering ReactUIView with props`, props);
ReactDOM.render<any>(<ReactUIView {...props} />, el as any);
}

scope.$on('$destroy', () => {
destroyed = true;
const unmounted = ReactDOM.unmountComponentAtNode(el);
// console.log(`${$id}: angular $destroy event -- unmountComponentAtNode(): ${unmounted}`, el);
const setChildViewProps = (scope as any).setChildViewProps;
if (setChildViewProps) {
setChildViewProps(null);
} else {
const unmounted = ReactDOM.unmountComponentAtNode(el);
// console.log(`${$id}: angular $destroy event -- unmountComponentAtNode(): ${unmounted}`, el);
}
// Remove using jQLite element for cross-browser compatibility.
elem.remove();
});
Expand All @@ -108,17 +116,3 @@ hybridModule.directive('reactUiViewAdapter', function() {
},
};
});

const InternalUIView = UIView.__internalViewComponent;

const ReactUIView = ({ refFn, ...props }) => (
<UIRouterContextComponent parentContextLevel="3">
<UIRouterConsumer>
{router => (
<UIViewConsumer>
{parentUiView => <InternalUIView {...props} ref={refFn} parentUIView={parentUiView} router={router} />}
</UIViewConsumer>
)}
</UIRouterConsumer>
</UIRouterContextComponent>
);
2 changes: 2 additions & 0 deletions src/react/AngularUIView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export class AngularUIView extends React.Component<any, any> {
this.state = {
$scope: $rootScope.$new(),
};

this.state.$scope.setChildViewProps = this.props.setChildViewProps;
}

render() {
Expand Down
19 changes: 19 additions & 0 deletions src/react/ReactUIView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { UIRouterConsumer, UIView, UIViewConsumer } from "@uirouter/react";
import { UIRouterContextComponent } from "./UIRouterReactContext";
import * as React from "react";

const InternalUIView = UIView.__internalViewComponent;

const ReactUIView = ({ refFn, ...props }) => (
<UIRouterContextComponent parentContextLevel="3" inherited={false}>
<UIRouterConsumer>
{router => (
<UIViewConsumer>
{parentUiView => <InternalUIView {...props} ref={refFn} parentUIView={parentUiView} router={router} />}
</UIViewConsumer>
)}
</UIRouterConsumer>
</UIRouterContextComponent>
);

export default ReactUIView;
7 changes: 5 additions & 2 deletions src/react/UIRouterReactContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import IInjectorService = angular.auto.IInjectorService;

export interface IUIRouterContextComponentProps {
parentContextLevel?: string;
inherited?: boolean;
}

export interface IUIRouterContextComponentState {
Expand All @@ -27,6 +28,7 @@ export class UIRouterContextComponent extends React.Component<
> {
public static defaultProps: Partial<IUIRouterContextComponentProps> = {
parentContextLevel: '0',
inherited: true,
};

public state: IUIRouterContextComponentState = {
Expand Down Expand Up @@ -71,13 +73,14 @@ export class UIRouterContextComponent extends React.Component<

private renderChild(child: ReactElement<any>) {
// console.log('renderChild()', child);
const inherited = this.props.inherited;
return (
<UIRouterConsumer>
{routerFromReactContext => (
<UIRouterProvider value={routerFromReactContext || this.state.router}>
<UIRouterProvider value={inherited && routerFromReactContext || this.state.router}>
<UIViewConsumer>
{parentUIViewFromReactContext => (
<UIViewProvider value={parentUIViewFromReactContext || this.state.parentUIViewAddress}>
<UIViewProvider value={inherited && parentUIViewFromReactContext || this.state.parentUIViewAddress}>
{child}
</UIViewProvider>
)}
Expand Down
38 changes: 37 additions & 1 deletion src/react/UIViewMonkeyPatch.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { UIView } from '@uirouter/react';
import { AngularUIView } from './AngularUIView';
import ReactUIView from "./ReactUIView";

/**
* Monkey patches the @uirouter/react UIView such that:
Expand All @@ -20,10 +22,44 @@ import { AngularUIView } from './AngularUIView';
*/
const realRender = UIView.prototype.render;

class PortalView extends React.PureComponent {
state = {
props: null,
target: null,
};

setChildViewProps = (props, target) => {
this.setState({ props, target });
};

renderPortal() {
if (!this.state) return null;
const { props, target } = this.state;
if (props && target) {
return ReactDOM.createPortal(
<ReactUIView {...props} />,
target
);
}
return null;
}

render() {
return (
<React.Fragment>
<AngularUIView {...this.props} setChildViewProps={this.setChildViewProps}/>
{this.renderPortal()}
</React.Fragment>
)
}
}

UIView.prototype.render = function() {
if (this.props.wrap === false) {
return realRender.apply(this, arguments);
}

return <AngularUIView {...this.props} />;
return (
<PortalView {...this.props}/>
);
};

0 comments on commit 5f76b16

Please sign in to comment.