Skip to content

Commit 0d789d9

Browse files
committed
2 parents 29d5195 + 0b8ad57 commit 0d789d9

File tree

6 files changed

+147
-14
lines changed

6 files changed

+147
-14
lines changed

docs/demo/getContainer.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## getContainer
2+
3+
4+
<code src="../examples/getContainer.tsx" />

docs/examples/getContainer.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import Portal from '../../src';
3+
4+
const Content = (): React.ReactElement => {
5+
React.useEffect(() => {
6+
console.log('Content Mount!');
7+
}, []);
8+
9+
return <>Bamboo</>;
10+
};
11+
12+
export default () => {
13+
const divRef = React.useRef();
14+
15+
return (
16+
<div
17+
ref={divRef}
18+
className="holder"
19+
style={{ minHeight: 10, border: '1px solid blue' }}
20+
>
21+
<Portal
22+
open
23+
getContainer={() => {
24+
return divRef.current;
25+
}}
26+
>
27+
<Content />
28+
</Portal>
29+
</div>
30+
);
31+
};

src/Portal.tsx

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import { inlineMock } from './mock';
88

99
export type ContainerType = Element | DocumentFragment;
1010

11-
export type GetContainer = string | ContainerType | (() => ContainerType);
11+
export type GetContainer =
12+
| string
13+
| ContainerType
14+
| (() => ContainerType)
15+
| false;
1216

1317
export interface PortalProps {
1418
/** Customize container element. Default will create a div in document.body when `open` */
@@ -18,10 +22,17 @@ export interface PortalProps {
1822
open?: boolean;
1923
/** Lock screen scroll when open */
2024
autoLock?: boolean;
25+
26+
/** @private debug name. Do not use in prod */
27+
debug?: string;
2128
}
2229

2330
const getPortalContainer = (getContainer: GetContainer) => {
24-
if (!canUseDom()) {
31+
if (getContainer === false) {
32+
return false;
33+
}
34+
35+
if (!canUseDom() || !getContainer) {
2536
return null;
2637
}
2738

@@ -35,7 +46,7 @@ const getPortalContainer = (getContainer: GetContainer) => {
3546
};
3647

3748
export default function Portal(props: PortalProps) {
38-
const { open, autoLock, getContainer, children } = props;
49+
const { open, autoLock, getContainer, debug, children } = props;
3950

4051
const [mergedRender, setMergedRender] = React.useState(open);
4152

@@ -47,23 +58,36 @@ export default function Portal(props: PortalProps) {
4758
}, [open]);
4859

4960
// ======================== Container ========================
50-
const customizeContainer = getPortalContainer(getContainer);
61+
const [innerContainer, setInnerContainer] = React.useState<
62+
ContainerType | false
63+
>(() => getPortalContainer(getContainer));
64+
65+
React.useEffect(() => {
66+
const customizeContainer = getPortalContainer(getContainer);
67+
68+
// Tell component that we check this in effect which is safe to be `null`
69+
setInnerContainer(customizeContainer ?? null);
70+
});
5171

5272
const [defaultContainer, queueCreate] = useDom(
53-
mergedRender && !customizeContainer,
73+
mergedRender && !innerContainer,
74+
debug,
5475
);
55-
const mergedContainer = customizeContainer || defaultContainer;
76+
const mergedContainer = innerContainer ?? defaultContainer;
5677

5778
// ========================= Render ==========================
5879
// Do not render when nothing need render
59-
if (!mergedRender || !canUseDom()) {
80+
// When innerContainer is `undefined`, it may not ready since user use ref in the same render
81+
if (!mergedRender || !canUseDom() || innerContainer === undefined) {
6082
return null;
6183
}
6284

63-
console.log(inlineMock());
85+
// Render inline
86+
const renderInline = mergedContainer === false || inlineMock();
87+
6488
return (
6589
<OrderContext.Provider value={queueCreate}>
66-
{inlineMock() ? children : createPortal(children, mergedContainer)}
90+
{renderInline ? children : createPortal(children, mergedContainer)}
6791
</OrderContext.Provider>
6892
);
6993
}

src/useDom.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,17 @@ const EMPTY_LIST = [];
99
* Will add `div` to document. Nest call will keep order
1010
* @param render Render DOM in document
1111
*/
12-
export default function useDom(render: boolean): [HTMLDivElement, QueueCreate] {
12+
export default function useDom(
13+
render: boolean,
14+
debug?: string,
15+
): [HTMLDivElement, QueueCreate] {
1316
const [ele] = React.useState(() => {
1417
const defaultEle = document.createElement('div');
18+
19+
if (process.env.NODE_ENV !== 'production' && debug) {
20+
defaultEle.setAttribute('data-debug', debug);
21+
}
22+
1523
return defaultEle;
1624
});
1725

@@ -22,7 +30,10 @@ export default function useDom(render: boolean): [HTMLDivElement, QueueCreate] {
2230
const mergedQueueCreate =
2331
queueCreate ||
2432
((appendFn: VoidFunction) => {
25-
setQueue(origin => [appendFn, ...origin]);
33+
setQueue(origin => {
34+
const newQueue = [appendFn, ...origin];
35+
return newQueue;
36+
});
2637
});
2738

2839
// =========================== DOM ===========================
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Portal getContainer customize in same level 1`] = `
4+
<div>
5+
<div
6+
class="holder"
7+
>
8+
Bamboo
9+
</div>
10+
</div>
11+
`;
12+
13+
exports[`Portal getContainer false 1`] = `
14+
<div>
15+
Hello
16+
Bamboo
17+
Light
18+
</div>
19+
`;

tests/index.test.tsx

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import Portal from '../src';
55
describe('Portal', () => {
66
it('Order', () => {
77
render(
8-
<Portal open>
8+
<Portal open debug="root">
99
<p>Root</p>
10-
<Portal open>
10+
<Portal open debug="parent">
1111
<p>Parent</p>
12-
<Portal open>
12+
<Portal open debug="children">
1313
<p>Children</p>
1414
</Portal>
1515
</Portal>
@@ -24,4 +24,48 @@ describe('Portal', () => {
2424
'Children',
2525
]);
2626
});
27+
28+
describe('getContainer', () => {
29+
it('false', () => {
30+
const { container } = render(
31+
<>
32+
Hello
33+
<Portal open getContainer={false}>
34+
Bamboo
35+
</Portal>
36+
Light
37+
</>,
38+
);
39+
40+
expect(container).toMatchSnapshot();
41+
});
42+
43+
it('customize in same level', () => {
44+
let renderTimes = 0;
45+
46+
const Content = () => {
47+
React.useEffect(() => {
48+
renderTimes += 1;
49+
});
50+
51+
return <>Bamboo</>;
52+
};
53+
54+
const Demo = () => {
55+
const divRef = React.useRef();
56+
57+
return (
58+
<div ref={divRef} className="holder">
59+
<Portal open getContainer={() => divRef.current}>
60+
<Content />
61+
</Portal>
62+
</div>
63+
);
64+
};
65+
66+
const { container } = render(<Demo />);
67+
expect(container).toMatchSnapshot();
68+
expect(renderTimes).toEqual(1);
69+
});
70+
});
2771
});

0 commit comments

Comments
 (0)