Skip to content

Commit 87f4310

Browse files
committed
chore(Input): use React.forwardRef() (#4267)
1 parent f36b36a commit 87f4310

File tree

4 files changed

+173
-168
lines changed

4 files changed

+173
-168
lines changed

src/elements/Input/Input.js

Lines changed: 112 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { handleRef } from '@fluentui/react-component-ref'
21
import cx from 'clsx'
32
import _ from 'lodash'
43
import PropTypes from 'prop-types'
5-
import React, { Children, cloneElement, Component, createRef } from 'react'
4+
import React from 'react'
65

76
import {
87
childrenUtils,
@@ -14,6 +13,7 @@ import {
1413
partitionHTMLProps,
1514
useKeyOnly,
1615
useValueAndKey,
16+
setRef,
1717
} from '../../lib'
1818
import Button from '../Button'
1919
import Icon from '../Icon'
@@ -26,146 +26,144 @@ import Label from '../Label'
2626
* @see Icon
2727
* @see Label
2828
*/
29-
class Input extends Component {
30-
inputRef = createRef()
31-
32-
computeIcon = () => {
33-
const { loading, icon } = this.props
29+
const Input = React.forwardRef(function (props, ref) {
30+
const {
31+
action,
32+
actionPosition,
33+
children,
34+
className,
35+
disabled,
36+
error,
37+
fluid,
38+
focus,
39+
icon,
40+
iconPosition,
41+
input,
42+
inverted,
43+
label,
44+
labelPosition,
45+
loading,
46+
size,
47+
tabIndex,
48+
transparent,
49+
type,
50+
} = props
51+
52+
const computeIcon = () => {
53+
if (!_.isNil(icon)) {
54+
return icon
55+
}
3456

35-
if (!_.isNil(icon)) return icon
36-
if (loading) return 'spinner'
57+
if (loading) {
58+
return 'spinner'
59+
}
3760
}
3861

39-
computeTabIndex = () => {
40-
const { disabled, tabIndex } = this.props
62+
const computeTabIndex = () => {
63+
if (!_.isNil(tabIndex)) {
64+
return tabIndex
65+
}
4166

42-
if (!_.isNil(tabIndex)) return tabIndex
43-
if (disabled) return -1
67+
if (disabled) {
68+
return -1
69+
}
4470
}
4571

46-
focus = () => this.inputRef.current.focus()
72+
const handleChange = (e) => {
73+
const newValue = _.get(e, 'target.value')
4774

48-
select = () => this.inputRef.current.select()
49-
50-
handleChange = (e) => {
51-
const value = _.get(e, 'target.value')
52-
53-
_.invoke(this.props, 'onChange', e, { ...this.props, value })
75+
_.invoke(props, 'onChange', e, { ...props, value: newValue })
5476
}
5577

56-
handleChildOverrides = (child, defaultProps) => ({
57-
...defaultProps,
58-
...child.props,
59-
ref: (c) => {
60-
handleRef(child.ref, c)
61-
this.inputRef.current = c
62-
},
63-
})
64-
65-
partitionProps = () => {
66-
const { disabled, type } = this.props
67-
68-
const tabIndex = this.computeTabIndex()
69-
const unhandled = getUnhandledProps(Input, this.props)
70-
const [htmlInputProps, rest] = partitionHTMLProps(unhandled)
78+
const partitionProps = () => {
79+
const unhandledProps = getUnhandledProps(Input, props)
80+
const [htmlInputProps, rest] = partitionHTMLProps(unhandledProps)
7181

7282
return [
7383
{
7484
...htmlInputProps,
7585
disabled,
7686
type,
77-
tabIndex,
78-
onChange: this.handleChange,
79-
ref: this.inputRef,
87+
tabIndex: computeTabIndex(),
88+
onChange: handleChange,
89+
ref,
8090
},
8191
rest,
8292
]
8393
}
8494

85-
render() {
86-
const {
87-
action,
88-
actionPosition,
89-
children,
90-
className,
91-
disabled,
92-
error,
93-
fluid,
94-
focus,
95-
icon,
96-
iconPosition,
97-
input,
98-
inverted,
99-
label,
100-
labelPosition,
101-
loading,
102-
size,
103-
transparent,
104-
type,
105-
} = this.props
106-
107-
const classes = cx(
108-
'ui',
109-
size,
110-
useKeyOnly(disabled, 'disabled'),
111-
useKeyOnly(error, 'error'),
112-
useKeyOnly(fluid, 'fluid'),
113-
useKeyOnly(focus, 'focus'),
114-
useKeyOnly(inverted, 'inverted'),
115-
useKeyOnly(loading, 'loading'),
116-
useKeyOnly(transparent, 'transparent'),
117-
useValueAndKey(actionPosition, 'action') || useKeyOnly(action, 'action'),
118-
useValueAndKey(iconPosition, 'icon') || useKeyOnly(icon || loading, 'icon'),
119-
useValueAndKey(labelPosition, 'labeled') || useKeyOnly(label, 'labeled'),
120-
'input',
121-
className,
122-
)
123-
const ElementType = getElementType(Input, this.props)
124-
const [htmlInputProps, rest] = this.partitionProps()
125-
126-
// Render with children
127-
// ----------------------------------------
128-
if (!childrenUtils.isNil(children)) {
129-
// add htmlInputProps to the `<input />` child
130-
const childElements = _.map(Children.toArray(children), (child) => {
131-
if (child.type !== 'input') return child
132-
return cloneElement(child, this.handleChildOverrides(child, htmlInputProps))
133-
})
134-
135-
return (
136-
<ElementType {...rest} className={classes}>
137-
{childElements}
138-
</ElementType>
139-
)
140-
}
141-
142-
// Render Shorthand
143-
// ----------------------------------------
144-
const actionElement = Button.create(action, { autoGenerateKey: false })
145-
const labelElement = Label.create(label, {
146-
defaultProps: {
147-
className: cx(
148-
'label',
149-
// add 'left|right corner'
150-
_.includes(labelPosition, 'corner') && labelPosition,
151-
),
152-
},
153-
autoGenerateKey: false,
95+
const classes = cx(
96+
'ui',
97+
size,
98+
useKeyOnly(disabled, 'disabled'),
99+
useKeyOnly(error, 'error'),
100+
useKeyOnly(fluid, 'fluid'),
101+
useKeyOnly(focus, 'focus'),
102+
useKeyOnly(inverted, 'inverted'),
103+
useKeyOnly(loading, 'loading'),
104+
useKeyOnly(transparent, 'transparent'),
105+
useValueAndKey(actionPosition, 'action') || useKeyOnly(action, 'action'),
106+
useValueAndKey(iconPosition, 'icon') || useKeyOnly(icon || loading, 'icon'),
107+
useValueAndKey(labelPosition, 'labeled') || useKeyOnly(label, 'labeled'),
108+
'input',
109+
className,
110+
)
111+
const ElementType = getElementType(Input, props)
112+
const [htmlInputProps, rest] = partitionProps()
113+
114+
// Render with children
115+
// ----------------------------------------
116+
if (!childrenUtils.isNil(children)) {
117+
// add htmlInputProps to the `<input />` child
118+
const childElements = _.map(React.Children.toArray(children), (child) => {
119+
if (child.type === 'input') {
120+
return React.cloneElement(child, {
121+
...htmlInputProps,
122+
...child.props,
123+
ref: (c) => {
124+
setRef(child.ref, c)
125+
setRef(ref, c)
126+
},
127+
})
128+
}
129+
130+
return child
154131
})
155132

156133
return (
157134
<ElementType {...rest} className={classes}>
158-
{actionPosition === 'left' && actionElement}
159-
{labelPosition !== 'right' && labelElement}
160-
{createHTMLInput(input || type, { defaultProps: htmlInputProps, autoGenerateKey: false })}
161-
{Icon.create(this.computeIcon(), { autoGenerateKey: false })}
162-
{actionPosition !== 'left' && actionElement}
163-
{labelPosition === 'right' && labelElement}
135+
{childElements}
164136
</ElementType>
165137
)
166138
}
167-
}
168139

140+
// Render Shorthand
141+
// ----------------------------------------
142+
const actionElement = Button.create(action, { autoGenerateKey: false })
143+
const labelElement = Label.create(label, {
144+
defaultProps: {
145+
className: cx(
146+
'label',
147+
// add 'left|right corner'
148+
_.includes(labelPosition, 'corner') && labelPosition,
149+
),
150+
},
151+
autoGenerateKey: false,
152+
})
153+
154+
return (
155+
<ElementType {...rest} className={classes}>
156+
{actionPosition === 'left' && actionElement}
157+
{labelPosition !== 'right' && labelElement}
158+
{createHTMLInput(input || type, { defaultProps: htmlInputProps, autoGenerateKey: false })}
159+
{Icon.create(computeIcon(), { autoGenerateKey: false })}
160+
{actionPosition !== 'left' && actionElement}
161+
{labelPosition === 'right' && labelElement}
162+
</ElementType>
163+
)
164+
})
165+
166+
Input.displayName = 'Input'
169167
Input.propTypes = {
170168
/** An element type to render as (string or function). */
171169
as: PropTypes.elementType,

src/lib/hooks/useMergedRefs.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import * as React from 'react'
22

33
/**
4+
* Assigns a value to a React ref.
5+
*
46
* @param {React.Ref} ref
57
* @param {HTMLElement} value
68
*/
7-
function setRef(ref, value) {
9+
export function setRef(ref, value) {
810
if (typeof ref === 'function') {
911
ref(value)
1012
} else if (ref) {

src/lib/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,5 @@ export useAutoControlledValue from './hooks/useAutoControlledValue'
5050
export useClassNamesOnNode from './hooks/useClassNamesOnNode'
5151
export useEventCallback from './hooks/useEventCallback'
5252
export useIsomorphicLayoutEffect from './hooks/useIsomorphicLayoutEffect'
53-
export useMergedRefs from './hooks/useMergedRefs'
53+
export useMergedRefs, { setRef } from './hooks/useMergedRefs'
5454
export usePrevious from './hooks/usePrevious'

0 commit comments

Comments
 (0)