Skip to content

Commit 2ac8009

Browse files
authored
fix(Portal): throw an error if React.Fragment passed as trigger (#3998)
1 parent 7889c7a commit 2ac8009

5 files changed

Lines changed: 47 additions & 11 deletions

File tree

src/addons/Portal/Portal.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import { handleRef, Ref } from '@stardust-ui/react-component-ref'
33
import keyboardKey from 'keyboard-key'
44
import _ from 'lodash'
55
import PropTypes from 'prop-types'
6-
import React, { cloneElement, createRef, Fragment } from 'react'
6+
import React from 'react'
77

88
import {
99
ModernAutoControlledComponent as Component,
1010
customPropTypes,
1111
doesNodeContainClick,
1212
makeDebugger,
1313
} from '../../lib'
14+
import validateTrigger from './utils/validateTrigger'
1415
import PortalInner from './PortalInner'
1516

1617
const debug = makeDebugger('portal')
@@ -126,8 +127,8 @@ class Portal extends Component {
126127

127128
static Inner = PortalInner
128129

129-
contentRef = createRef()
130-
triggerRef = createRef()
130+
contentRef = React.createRef()
131+
triggerRef = React.createRef()
131132
latestDocumentMouseDownEvent = null
132133

133134
componentWillUnmount() {
@@ -335,10 +336,15 @@ class Portal extends Component {
335336
const { children, eventPool, mountNode, trigger } = this.props
336337
const { open } = this.state
337338

339+
/* istanbul ignore else */
340+
if (process.env.NODE_ENV !== 'production') {
341+
validateTrigger(trigger)
342+
}
343+
338344
return (
339-
<Fragment>
345+
<>
340346
{open && (
341-
<Fragment>
347+
<>
342348
<PortalInner
343349
innerRef={this.contentRef}
344350
mountNode={mountNode}
@@ -363,11 +369,11 @@ class Portal extends Component {
363369
<EventStack name='mousedown' on={this.handleDocumentMouseDown} pool={eventPool} />
364370
<EventStack name='click' on={this.handleDocumentClick} pool={eventPool} />
365371
<EventStack name='keydown' on={this.handleEscape} pool={eventPool} />
366-
</Fragment>
372+
</>
367373
)}
368374
{trigger && (
369375
<Ref innerRef={this.handleTriggerRef}>
370-
{cloneElement(trigger, {
376+
{React.cloneElement(trigger, {
371377
onBlur: this.handleTriggerBlur,
372378
onClick: this.handleTriggerClick,
373379
onFocus: this.handleTriggerFocus,
@@ -376,7 +382,7 @@ class Portal extends Component {
376382
})}
377383
</Ref>
378384
)}
379-
</Fragment>
385+
</>
380386
)
381387
}
382388
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react'
2+
import * as ReactIs from 'react-is'
3+
4+
/**
5+
* Asserts that a passed element can be used cloned a props will be applied properly.
6+
*/
7+
export default function validateTrigger(element) {
8+
if (element) {
9+
React.Children.only(element)
10+
11+
if (ReactIs.isFragment(element)) {
12+
throw new Error('An "React.Fragment" cannot be used as a `trigger`.')
13+
}
14+
}
15+
}

src/modules/Popup/Popup.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,9 @@ export default class Popup extends Component {
341341
} = this.props
342342
const { closed, portalRestProps } = this.state
343343

344-
if (closed || disabled) return trigger
344+
if (closed || disabled) {
345+
return trigger
346+
}
345347

346348
const modifiers = _.merge(
347349
{

test/specs/addons/Portal/Portal-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import _ from 'lodash'
22
import PropTypes from 'prop-types'
3-
import React, { Component } from 'react'
3+
import React from 'react'
44

55
import * as common from 'test/specs/commonTests'
66
import { domEvent, sandbox } from 'test/utils'
@@ -10,7 +10,7 @@ import PortalInner from 'src/addons/Portal/PortalInner'
1010
let wrapper
1111

1212
const createHandlingComponent = (eventName) =>
13-
class HandlingComponent extends Component {
13+
class HandlingComponent extends React.Component {
1414
static propTypes = {
1515
handler: PropTypes.func,
1616
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from 'react'
2+
3+
import validateTrigger from 'src/addons/Portal/utils/validateTrigger'
4+
5+
describe('validateTrigger', () => {
6+
it('throws on multiple elements passed', () => {
7+
expect(() => validateTrigger([<button key='trigger1' />, <button key='trigger2' />])).to.throw()
8+
})
9+
10+
it('throws on React.Fragment passed', () => {
11+
expect(() => validateTrigger(React.createElement(React.Fragment))).to.throw()
12+
})
13+
})

0 commit comments

Comments
 (0)