Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 47 additions & 17 deletions docs/src/examples/addons/Ref/Types/RefExampleRef.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,66 @@
import React, { Component } from 'react'
import React, { Component, createRef } from 'react'
import { Grid, Table, Ref, Segment } from 'semantic-ui-react'

export default class RefExampleRef extends Component {
state = {}

handleRef = node => this.setState({ node })
createdRef = createRef()
functionalRef = null

handleRef = (node) => {
this.functionalRef = node
}

componentDidMount() {
// eslint-disable-next-line react/no-did-mount-set-state
this.setState({ isMounted: true })
}

render() {
const { node } = this.state
const { isMounted } = this.state

return (
<Grid columns={2}>
<Grid.Column>
<Ref innerRef={this.handleRef}>
<Segment>An example node</Segment>
</Ref>
<Grid>
<Grid.Column width={6}>
<Segment.Group>
<Ref innerRef={this.handleRef}>
<Segment>An example node with functional ref</Segment>
</Ref>
<Ref innerRef={this.createdRef}>
<Segment>
An example node with ref from <code>createRef()</code>
</Segment>
</Ref>
</Segment.Group>
</Grid.Column>
<Grid.Column>
{node && (
<Grid.Column width={10}>
{isMounted && (
<Table>
<Table.Body>
<Table.Header>
<Table.Row>
<Table.Cell>nodeName</Table.Cell>
<Table.Cell>{node.nodeName}</Table.Cell>
<Table.HeaderCell>Type</Table.HeaderCell>
<Table.HeaderCell>
<code>nodeName</code>
</Table.HeaderCell>
<Table.HeaderCell>
<code>textContent</code>
</Table.HeaderCell>
</Table.Row>
</Table.Header>

<Table.Body>
<Table.Row>
<Table.Cell>nodeType</Table.Cell>
<Table.Cell>{node.nodeType}</Table.Cell>
<Table.Cell>Functional ref</Table.Cell>
<Table.Cell>{this.functionalRef.nodeName}</Table.Cell>
<Table.Cell>{this.functionalRef.textContent}</Table.Cell>
</Table.Row>

<Table.Row>
<Table.Cell>textContent</Table.Cell>
<Table.Cell>{node.textContent}</Table.Cell>
<Table.Cell singleLine>
From <code>createRef()</code>
</Table.Cell>
<Table.Cell>{this.createdRef.current.nodeName}</Table.Cell>
<Table.Cell>{this.createdRef.current.textContent}</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
Expand Down
5 changes: 3 additions & 2 deletions src/addons/Portal/Portal.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
AutoControlledComponent as Component,
doesNodeContainClick,
eventStack,
handleRef,
makeDebugger,
} from '../../lib'
import Ref from '../Ref'
Expand Down Expand Up @@ -115,7 +116,7 @@ class Portal extends Component {
*
* @param {HTMLElement} node - Referred node.
*/
triggerRef: PropTypes.func,
triggerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
}

static defaultProps = {
Expand Down Expand Up @@ -334,7 +335,7 @@ class Portal extends Component {

handleTriggerRef = (c) => {
this.triggerNode = c
_.invoke(this.props, 'triggerRef', c)
handleRef(this.props.triggerRef, c)
}

render() {
Expand Down
2 changes: 1 addition & 1 deletion src/addons/Ref/Ref.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface StrictRefProps {
*
* @param {HTMLElement} node - Referred node.
*/
innerRef?: (node: HTMLElement) => void
innerRef?: React.Ref<any>
}

declare class Ref extends React.Component<RefProps, {}> {}
Expand Down
16 changes: 9 additions & 7 deletions src/addons/Ref/Ref.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import PropTypes from 'prop-types'
import { Children, Component } from 'react'
import { findDOMNode } from 'react-dom'

import handleRef from '../../lib/handleRef'

/**
* This component exposes a callback prop that always returns the DOM node of both functional and class component
* children.
Expand All @@ -12,20 +14,20 @@ export default class Ref extends Component {
children: PropTypes.element,

/**
* Called when componentDidMount.
* Called when a child component will be mounted or updated.
*
* @param {HTMLElement} node - Referred node.
*/
innerRef: PropTypes.func,
innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
}

componentDidMount() {
const { innerRef } = this.props

// Heads up! Don't move this condition, it's a short circuit that avoids run of `findDOMNode`
// if `innerRef` isn't passed
// eslint-disable-next-line react/no-find-dom-node
if (innerRef) innerRef(findDOMNode(this))
handleRef(this.props.innerRef, findDOMNode(this))
}

componentWillUnmount() {
handleRef(this.props.innerRef, null)
}

render() {
Expand Down
3 changes: 2 additions & 1 deletion src/elements/Input/Input.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
customPropTypes,
getElementType,
getUnhandledProps,
handleRef,
partitionHTMLProps,
SUI,
useKeyOnly,
Expand Down Expand Up @@ -129,7 +130,7 @@ class Input extends Component {
...defaultProps,
...child.props,
ref: (c) => {
_.invoke(child, 'ref', c)
handleRef(child.ref, c)
this.handleInputRef(c)
},
})
Expand Down
31 changes: 31 additions & 0 deletions src/lib/handleRef.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* The function that correctly handles passing refs.
*
* @param {Function|Object} ref An ref object or function
* @param {HTMLElement} node A node that should be passed by ref
*/
const handleRef = (ref, node) => {
if (process.env.NODE_ENV !== 'production') {
if (typeof ref === 'string') {
throw new Error(
[
'We do not support refs as string, this is a legacy API and will be likely to be removed in',
'one of the future releases of React.',
].join(' '),
)
}
}

if (typeof ref === 'function') {
ref(node)
return
}

if (ref !== null && typeof ref === 'object') {
// The `current` property is defined as readonly, however it's a valid way because `ref` is a mutable object
// eslint-disable-next-line no-param-reassign
ref.current = node
}
}

export default handleRef
1 change: 1 addition & 0 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export eventStack from './eventStack'
export * from './factories'
export getUnhandledProps from './getUnhandledProps'
export getElementType from './getElementType'
export handleRef from './handleRef'

export {
htmlInputAttrs,
Expand Down
2 changes: 1 addition & 1 deletion src/modules/Dropdown/DropdownSearchInput.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface StrictDropdownSearchInputProps {
className?: string

/** A ref handler for input. */
inputRef?: (c: HTMLInputElement) => void
inputRef?: React.Ref<HTMLInputElement>

/** An input can receive focus. */
tabIndex?: number | string
Expand Down
8 changes: 5 additions & 3 deletions src/modules/Dropdown/DropdownSearchInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import _ from 'lodash'
import PropTypes from 'prop-types'
import React, { Component } from 'react'

import { createShorthandFactory, customPropTypes, getUnhandledProps } from '../../lib'
import { createShorthandFactory, customPropTypes, getUnhandledProps, handleRef } from '../../lib'

/**
* A search item sub-component for Dropdown component.
Expand All @@ -20,7 +20,7 @@ class DropdownSearchInput extends Component {
className: PropTypes.string,

/** A ref handler for input. */
inputRef: PropTypes.func,
inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),

/** An input can receive focus. */
tabIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
Expand All @@ -43,7 +43,9 @@ class DropdownSearchInput extends Component {
_.invoke(this.props, 'onChange', e, { ...this.props, value })
}

handleRef = c => _.invoke(this.props, 'inputRef', c)
handleRef = (c) => {
handleRef(this.props.inputRef, c)
}

render() {
const { autoComplete, className, tabIndex, type, value } = this.props
Expand Down
34 changes: 34 additions & 0 deletions test/specs/lib/handleRef-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react'

import handleRef from 'src/lib/handleRef'
import { sandbox } from 'test/utils'

describe('handleRef', () => {
it('throws an error when "ref" is string', () => {
expect(() => {
handleRef('ref', document.createElement('div'))
}).to.throw()
})

it('does not do anything when "ref" is null', () => {
expect(() => {
handleRef(null, document.createElement('div'))
}).to.not.throw()
})

it('calls with node when "ref" is function', () => {
const ref = sandbox.spy()
const node = document.createElement('div')

handleRef(ref, node)
ref.should.have.calledWith(node)
})

it('assigns to "current" when "ref" is object', () => {
const ref = React.createRef()
const node = document.createElement('div')

handleRef(ref, node)
ref.should.have.property('current', node)
})
})