Skip to content

Commit

Permalink
refactor: Portal render sync (#150)
Browse files Browse the repository at this point in the history
* refactor: Make portal sync

* add example

* add examples

* add test case

* should use container check
  • Loading branch information
zombieJ committed Sep 24, 2020
1 parent d2241a9 commit d9e1639
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 48 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,3 +1,4 @@
.storybook
*.iml
*.log
.idea
Expand Down
18 changes: 18 additions & 0 deletions examples/portal.tsx
@@ -0,0 +1,18 @@
import React from 'react';
import PortalWrapper from '../src/PortalWrapper';

export default () => {
const divRef = React.useRef();

React.useEffect(() => {
console.log('>>>', divRef.current);
}, []);

return (
<>
<PortalWrapper forceRender>
{() => <div ref={divRef}>2333</div>}
</PortalWrapper>
</>
);
};
5 changes: 2 additions & 3 deletions package.json
Expand Up @@ -19,10 +19,8 @@
"url": "http://github.com/react-component/util/issues"
},
"license": "MIT",
"config": {
"port": 8100
},
"scripts": {
"start": "cross-env NODE_ENV=development father doc dev --storybook",
"lint": "eslint src/ --ext .tsx,.ts & eslint tests/ --ext .js",
"compile": "father build",
"prepublishOnly": "npm run compile && np --yolo --no-publish",
Expand All @@ -38,6 +36,7 @@
"@umijs/fabric": "^2.0.8",
"coveralls": "^3.1.0",
"create-react-class": "^15.6.3",
"cross-env": "^7.0.2",
"enzyme": "^3.10.0",
"eslint": "^6.6.0",
"father": "^2.14.0",
Expand Down
37 changes: 0 additions & 37 deletions src/Portal.js

This file was deleted.

46 changes: 46 additions & 0 deletions src/Portal.tsx
@@ -0,0 +1,46 @@
import * as React from 'react';
import { useRef, forwardRef, useImperativeHandle } from 'react';
import ReactDOM from 'react-dom';
import canUseDom from './Dom/canUseDom';

export type PortalRef = {};

export interface PortalProps {
/** @deprecated Not know who use this? */
didUpdate?: (prevProps: PortalProps) => void;
getContainer: () => HTMLElement;
children?: React.ReactNode;
}

const Portal = forwardRef<PortalRef, PortalProps>((props, ref) => {
const { didUpdate, getContainer, children } = props;

const containerRef = useRef<HTMLElement>();

// Ref return nothing, only for wrapper check exist
useImperativeHandle(ref, () => ({}));

// Create container in client side with sync to avoid useEffect not get ref
const initRef = useRef(false);
if (!initRef.current && canUseDom()) {
containerRef.current = getContainer();
initRef.current = true;
}

// Not know who use this. Just keep it here
React.useEffect(() => {
didUpdate?.(props);

return () => {
if (containerRef.current) {
containerRef.current.parentNode.removeChild(containerRef.current);
}
};
}, []);

return containerRef.current
? ReactDOM.createPortal(children, containerRef.current)
: null;
});

export default Portal;
50 changes: 42 additions & 8 deletions src/PortalWrapper.js → src/PortalWrapper.tsx
@@ -1,8 +1,8 @@
/* eslint-disable no-underscore-dangle,react/require-default-props */
import React from 'react';
import ReactDOM from 'react-dom';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import ContainerRender from './ContainerRender';
import Portal from './Portal';
import Portal, { PortalRef } from './Portal';
import switchScrollingEffect from './switchScrollingEffect';
import setStyle from './setStyle';

Expand All @@ -19,7 +19,7 @@ const IS_REACT_16 = 'createPortal' in ReactDOM;
// https://github.com/ant-design/ant-design/issues/19332
let cacheOverflow = {};

const getParent = getContainer => {
const getParent = (getContainer: GetContainer) => {
if (windowIsUndefined) {
return null;
}
Expand All @@ -40,8 +40,42 @@ const getParent = getContainer => {
return document.body;
};

class PortalWrapper extends React.Component {
constructor(props) {
export type GetContainer = string | HTMLElement | (() => HTMLElement);

export interface PortalWrapperProps {
visible?: boolean;
getContainer?: GetContainer;
wrapperClassName?: string;
forceRender?: boolean;
children: (info: {
getOpenCount: () => number;
getContainer: () => HTMLElement;
switchScrollingEffect: () => void;
ref?: (c: any) => void;
}) => React.ReactNode;
}

export interface PortalWrapperState {
_self: PortalWrapper;
}

class PortalWrapper extends React.Component<
PortalWrapperProps,
PortalWrapperState
> {
container?: HTMLElement;

_component?: PortalRef;

renderComponent?: (info: {
afterClose: Function;
onClose: Function;
visible: boolean;
}) => void;

removeContainer?: Function;

constructor(props: PortalWrapperProps) {
super(props);
const { visible, getContainer } = props;
if (!windowIsUndefined && getParent(getContainer) === document.body) {
Expand Down Expand Up @@ -121,7 +155,7 @@ class PortalWrapper extends React.Component {
}
};

savePortal = c => {
savePortal = (c: PortalRef) => {
// Warning: don't rename _component
// https://github.com/react-component/util/pull/65#discussion_r352407916
this._component = c;
Expand Down Expand Up @@ -175,7 +209,7 @@ class PortalWrapper extends React.Component {
getContainer: this.getContainer,
switchScrollingEffect: this.switchScrollingEffect,
};
// suppport react15
// support react15
if (!IS_REACT_16) {
return (
<ContainerRender
Expand Down
30 changes: 30 additions & 0 deletions tests/Portal.test.tsx
@@ -0,0 +1,30 @@
import React from 'react';
import { mount } from 'enzyme';
import PortalWrapper from '../src/PortalWrapper';

describe('Portal', () => {
let container: HTMLDivElement;

beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});

afterEach(() => {
document.body.removeChild(container);
});

it('forceRender', () => {
const divRef = React.createRef<any>();

const wrapper = mount(
<PortalWrapper forceRender>
{() => <div ref={divRef}>2333</div>}
</PortalWrapper>,
);

expect(divRef.current).toBeTruthy();

wrapper.unmount();
});
});

0 comments on commit d9e1639

Please sign in to comment.