From b7c4065624d414a05880af3631cf24d9bf8bbe28 Mon Sep 17 00:00:00 2001 From: zombiej Date: Wed, 23 Sep 2020 20:58:28 +0800 Subject: [PATCH 1/5] refactor: Make portal sync --- src/Portal.js | 37 ----------------- src/Portal.tsx | 40 ++++++++++++++++++ src/{PortalWrapper.js => PortalWrapper.tsx} | 46 ++++++++++++++++++--- 3 files changed, 80 insertions(+), 43 deletions(-) delete mode 100644 src/Portal.js create mode 100644 src/Portal.tsx rename src/{PortalWrapper.js => PortalWrapper.tsx} (86%) diff --git a/src/Portal.js b/src/Portal.js deleted file mode 100644 index 8a7bb50a..00000000 --- a/src/Portal.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; - -export default class Portal extends React.Component { - componentDidMount() { - this.createContainer(); - } - - componentDidUpdate(prevProps) { - const { didUpdate } = this.props; - if (didUpdate) { - didUpdate(prevProps); - } - } - - componentWillUnmount() { - this.removeContainer(); - } - - createContainer() { - this._container = this.props.getContainer(); - this.forceUpdate(); - } - - removeContainer() { - if (this._container) { - this._container.parentNode.removeChild(this._container); - } - } - - render() { - if (this._container) { - return ReactDOM.createPortal(this.props.children, this._container); - } - return null; - } -} diff --git a/src/Portal.tsx b/src/Portal.tsx new file mode 100644 index 00000000..3b1d34bb --- /dev/null +++ b/src/Portal.tsx @@ -0,0 +1,40 @@ +import * as React from 'react'; +import { useRef } from 'react'; +import ReactDOM from 'react-dom'; +import canUseDom from './Dom/canUseDom'; + +export interface PortalProps { + /** @deprecated Not know who use this? */ + didUpdate?: (prevProps: PortalProps) => void; + getContainer: () => HTMLElement; + children?: React.ReactNode; +} + +const Portal = (props: PortalProps) => { + const { didUpdate, getContainer, children } = props; + + const containerRef = useRef(); + + // Create container in client side with sync to avoid useEffect not get ref + const initRef = useRef(false); + if (!initRef.current && canUseDom()) { + containerRef.current = getContainer(); + } + + // 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; diff --git a/src/PortalWrapper.js b/src/PortalWrapper.tsx similarity index 86% rename from src/PortalWrapper.js rename to src/PortalWrapper.tsx index 27b1067d..7af08f37 100644 --- a/src/PortalWrapper.js +++ b/src/PortalWrapper.tsx @@ -1,6 +1,6 @@ /* 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 switchScrollingEffect from './switchScrollingEffect'; @@ -40,8 +40,42 @@ const getParent = getContainer => { return document.body; }; -class PortalWrapper extends React.Component { - constructor(props) { +export type GetContainer = 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?: any; + + 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) { @@ -121,7 +155,7 @@ class PortalWrapper extends React.Component { } }; - savePortal = c => { + savePortal = (c: any) => { // Warning: don't rename _component // https://github.com/react-component/util/pull/65#discussion_r352407916 this._component = c; @@ -175,7 +209,7 @@ class PortalWrapper extends React.Component { getContainer: this.getContainer, switchScrollingEffect: this.switchScrollingEffect, }; - // suppport react15 + // support react15 if (!IS_REACT_16) { return ( Date: Wed, 23 Sep 2020 21:05:22 +0800 Subject: [PATCH 2/5] add example --- examples/portal.tsx | 10 ++++++++++ package.json | 5 ++--- src/Portal.tsx | 11 ++++++++--- src/PortalWrapper.tsx | 10 +++++----- 4 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 examples/portal.tsx diff --git a/examples/portal.tsx b/examples/portal.tsx new file mode 100644 index 00000000..1e8c8364 --- /dev/null +++ b/examples/portal.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import PortalWrapper from '../src/PortalWrapper'; + +export default () => { + return ( + <> + {() =>
2333
}
+ + ); +}; diff --git a/package.json b/package.json index 6b02a840..fe7716c6 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", diff --git a/src/Portal.tsx b/src/Portal.tsx index 3b1d34bb..bf5dac5e 100644 --- a/src/Portal.tsx +++ b/src/Portal.tsx @@ -1,8 +1,10 @@ import * as React from 'react'; -import { useRef } 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; @@ -10,11 +12,14 @@ export interface PortalProps { children?: React.ReactNode; } -const Portal = (props: PortalProps) => { +const Portal = forwardRef((props, ref) => { const { didUpdate, getContainer, children } = props; const containerRef = useRef(); + // 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()) { @@ -35,6 +40,6 @@ const Portal = (props: PortalProps) => { return containerRef.current ? ReactDOM.createPortal(children, containerRef.current) : null; -}; +}); export default Portal; diff --git a/src/PortalWrapper.tsx b/src/PortalWrapper.tsx index 7af08f37..f5193577 100644 --- a/src/PortalWrapper.tsx +++ b/src/PortalWrapper.tsx @@ -2,7 +2,7 @@ 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'; @@ -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; } @@ -40,7 +40,7 @@ const getParent = getContainer => { return document.body; }; -export type GetContainer = HTMLElement; +export type GetContainer = string | HTMLElement | (() => HTMLElement); export interface PortalWrapperProps { visible?: boolean; @@ -65,7 +65,7 @@ class PortalWrapper extends React.Component< > { container?: HTMLElement; - _component?: any; + _component?: PortalRef; renderComponent?: (info: { afterClose: Function; @@ -155,7 +155,7 @@ class PortalWrapper extends React.Component< } }; - savePortal = (c: any) => { + savePortal = (c: PortalRef) => { // Warning: don't rename _component // https://github.com/react-component/util/pull/65#discussion_r352407916 this._component = c; From 31c089c081010412e8a2180e7f857e31b88ffe3e Mon Sep 17 00:00:00 2001 From: zombiej Date: Wed, 23 Sep 2020 21:17:41 +0800 Subject: [PATCH 3/5] add examples --- .gitignore | 1 + examples/portal.tsx | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5114de1e..8ca35990 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.storybook *.iml *.log .idea diff --git a/examples/portal.tsx b/examples/portal.tsx index 1e8c8364..3e92560c 100644 --- a/examples/portal.tsx +++ b/examples/portal.tsx @@ -2,9 +2,17 @@ import React from 'react'; import PortalWrapper from '../src/PortalWrapper'; export default () => { + const divRef = React.useRef(); + + React.useEffect(() => { + console.log('>>>', divRef.current); + }, []); + return ( <> - {() =>
2333
}
+ + {() =>
2333
} +
); }; From b65f17af5bcc01e1cffaae77bbb8bb89c7718a51 Mon Sep 17 00:00:00 2001 From: zombiej Date: Wed, 23 Sep 2020 21:22:23 +0800 Subject: [PATCH 4/5] add test case --- tests/Portal.test.tsx | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests/Portal.test.tsx diff --git a/tests/Portal.test.tsx b/tests/Portal.test.tsx new file mode 100644 index 00000000..a4e82526 --- /dev/null +++ b/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(); + + const wrapper = mount( + + {() =>
2333
} +
, + ); + + expect(divRef.current).toBeTruthy(); + + wrapper.unmount(); + }); +}); From c8022062fbf72ca59268d9d278ea1f333c5200e2 Mon Sep 17 00:00:00 2001 From: zombiej Date: Thu, 24 Sep 2020 10:04:27 +0800 Subject: [PATCH 5/5] should use container check --- src/Portal.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Portal.tsx b/src/Portal.tsx index bf5dac5e..af1d9148 100644 --- a/src/Portal.tsx +++ b/src/Portal.tsx @@ -24,6 +24,7 @@ const Portal = forwardRef((props, ref) => { const initRef = useRef(false); if (!initRef.current && canUseDom()) { containerRef.current = getContainer(); + initRef.current = true; } // Not know who use this. Just keep it here