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 new file mode 100644 index 00000000..3e92560c --- /dev/null +++ b/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 ( + <> + + {() =>
2333
} +
+ + ); +}; diff --git a/package.json b/package.json index b0314699..fda026fa 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.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..af1d9148 --- /dev/null +++ b/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((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()) { + 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; diff --git a/src/PortalWrapper.js b/src/PortalWrapper.tsx similarity index 84% rename from src/PortalWrapper.js rename to src/PortalWrapper.tsx index 27b1067d..f5193577 100644 --- a/src/PortalWrapper.js +++ b/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'; @@ -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,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) { @@ -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; @@ -175,7 +209,7 @@ class PortalWrapper extends React.Component { getContainer: this.getContainer, switchScrollingEffect: this.switchScrollingEffect, }; - // suppport react15 + // support react15 if (!IS_REACT_16) { return ( { + 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(); + }); +});