Skip to content

Commit 27eed7b

Browse files
committed
Remove onReady callback and create element at portal-creating time instead
1 parent d69b424 commit 27eed7b

File tree

1 file changed

+26
-33
lines changed

1 file changed

+26
-33
lines changed

src/index.tsx

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ type Component<P> = React.Component<P> | React.ComponentType<P>;
99

1010
type ComponentProps<C extends Component<any>> = C extends Component<infer P> ? P : never;
1111

12+
type PortalNodeElementType = 'html' | 'svg';
1213
type PortalNodeElement = HTMLElement | SVGElement;
1314

1415
export interface PortalNode<C extends Component<any> = Component<any>> {
1516
// The dom element used for the React portal. Created on portal mount.
16-
element: PortalNodeElement | null,
17-
onReady: (() => void) | null,
17+
element: PortalNodeElement,
18+
elementType: PortalNodeElementType,
1819
// Used by the out portal to send props back to the real element
1920
// Hooked by InPortal to become a state update (and thus rerender)
2021
setPortalProps(p: ComponentProps<C>): void;
@@ -33,15 +34,26 @@ interface InPortalProps {
3334
children: React.ReactNode;
3435
}
3536

36-
export const createPortalNode = <C extends Component<any>>(): PortalNode<C> => {
37+
export const createPortalNode = <C extends Component<any>>(elementType: PortalNodeElementType): PortalNode<C> => {
3738
let initialProps = {} as ComponentProps<C>;
3839

3940
let parent: Node | undefined;
4041
let lastPlaceholder: Node | undefined;
4142

43+
const isHtmlPortal = elementType==='html';
44+
const isSvgPortal=elementType==='svg'
45+
let element;
46+
if (isHtmlPortal) {
47+
element= document.createElement('div');
48+
} else if (isSvgPortal){
49+
element= document.createElementNS(SVG_NAMESPACE, 'g');
50+
} else {
51+
throw new Error(`Invalid element type "${elementType}" for createPortalNode: must be "html" or "svg".`)
52+
}
53+
4254
const portalNode: PortalNode = {
43-
element: null,
44-
onReady: null,
55+
element,
56+
elementType,
4557
setPortalProps: (props: ComponentProps<C>) => {
4658
initialProps = props;
4759
},
@@ -55,13 +67,13 @@ export const createPortalNode = <C extends Component<any>>(): PortalNode<C> => {
5567
}
5668
portalNode.unmount();
5769

58-
// To support SVG and other non-html elements, the portalNode's element needs to be
59-
// created with the correct namespace.
60-
if (!portalNode.element || portalNode.element.tagName !== newParent.tagName) {
61-
if (newParent instanceof SVGElement) {
62-
portalNode.element = document.createElementNS(SVG_NAMESPACE, newParent.tagName);
63-
} else {
64-
portalNode.element = document.createElement(newParent.tagName);
70+
// To support SVG and other non-html elements, the portalNode's elementType needs to match
71+
// the elementType it's being rendered into
72+
if (newParent !== parent) {
73+
console.log('newParent: ', newParent)
74+
if ((isHtmlPortal && !(newParent instanceof HTMLElement)) ||
75+
(isSvgPortal && !(newParent instanceof SVGElement))) {
76+
throw new Error(`Invalid element type for portal: "${elementType}" portalNodes must be used with ${elementType} elements.`)
6577
}
6678
}
6779

@@ -95,13 +107,12 @@ export const createPortalNode = <C extends Component<any>>(): PortalNode<C> => {
95107
return portalNode;
96108
};
97109

98-
export class InPortal extends React.PureComponent<InPortalProps, { nodeProps: {}, onReadyFired: boolean }> {
110+
export class InPortal extends React.PureComponent<InPortalProps, { nodeProps: {} }> {
99111

100112
constructor(props: InPortalProps) {
101113
super(props);
102114
this.state = {
103115
nodeProps: this.props.node.getInitialPortalProps(),
104-
onReadyFired: false,
105116
};
106117
}
107118

@@ -125,14 +136,6 @@ export class InPortal extends React.PureComponent<InPortalProps, { nodeProps: {}
125136
render() {
126137
const { children, node } = this.props;
127138

128-
if (!node.element) {
129-
// The OutPortal has not yet determined whether or not we're in a special namespace like SVG:
130-
// delay our rendering for now, and attach an onReady callback which the OutPortal can use to
131-
// trigger a rerender once we're ready
132-
node.onReady = () => this.setState({ onReadyFired: true })
133-
return null;
134-
}
135-
136139
return ReactDOM.createPortal(
137140
React.Children.map(children, (child) => {
138141
if (!React.isValidElement(child)) return child;
@@ -157,15 +160,6 @@ export class OutPortal<C extends Component<any>> extends React.PureComponent<Out
157160
this.passPropsThroughPortal();
158161
}
159162

160-
maybeTriggerOnReady() {
161-
if (this.props.node.onReady) {
162-
// There's an InPortal which is waiting on this OutPortal:
163-
// rerender it now that the OutPortal and portalNode are ready
164-
this.props.node.onReady();
165-
this.props.node.onReady = null;
166-
}
167-
}
168-
169163
passPropsThroughPortal() {
170164
const propsForTarget = Object.assign({}, this.props, { node: undefined });
171165
this.props.node.setPortalProps(propsForTarget);
@@ -179,7 +173,6 @@ export class OutPortal<C extends Component<any>> extends React.PureComponent<Out
179173
const parent = placeholder.parentNode!;
180174
node.mount(parent, placeholder);
181175
this.passPropsThroughPortal();
182-
this.maybeTriggerOnReady();
183176
}
184177

185178
componentDidUpdate() {
@@ -197,7 +190,6 @@ export class OutPortal<C extends Component<any>> extends React.PureComponent<Out
197190
const parent = placeholder.parentNode!;
198191
node.mount(parent, placeholder);
199192
this.passPropsThroughPortal();
200-
this.maybeTriggerOnReady();
201193
}
202194

203195
componentWillUnmount() {
@@ -208,6 +200,7 @@ export class OutPortal<C extends Component<any>> extends React.PureComponent<Out
208200
render() {
209201
// Render a placeholder to the DOM, so we can get a reference into
210202
// our location in the DOM, and swap it out for the portaled node.
203+
// A <div> placeholder works fine even for SVG.
211204
return <div ref={this.placeholderNode} />;
212205
}
213206

0 commit comments

Comments
 (0)