Skip to content

Commit

Permalink
[core] Add React.createRef support (mui#11757)
Browse files Browse the repository at this point in the history
* feat: make input accept both ref callback and ref from React.createRef

* feat: update [TextArea] to accept React.createRef object as inputRef

* feat: update typescript and proptypes declaration for inputRef

* test: add inputRef test for input

* simpler logic
  • Loading branch information
t49tran authored and Joe Shelby committed Jul 14, 2018
1 parent 045fa94 commit 0b06244
Show file tree
Hide file tree
Showing 24 changed files with 84 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ module.exports = [
name: 'The size of all the modules of material-ui.',
webpack: true,
path: 'packages/material-ui/build/index.js',
limit: '94.6 KB',
limit: '94.7 KB',
},
{
name: 'The main bundle of the docs',
Expand Down
2 changes: 1 addition & 1 deletion packages/material-ui/src/Checkbox/Checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Checkbox.propTypes = {
/**
* Use that property to pass a ref callback to the native input component.
*/
inputRef: PropTypes.func,
inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
/**
* Callback fired when the state is changed.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ FormControlLabel.propTypes = {
/**
* Use that property to pass a ref callback to the native input component.
*/
inputRef: PropTypes.func,
inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
/**
* The text to be used in an enclosing label element.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/material-ui/src/Input/Input.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface InputProps
id?: string;
inputComponent?: React.ReactType<InputComponentProps>;
inputProps?: { [arbitrary: string]: any };
inputRef?: React.Ref<any>;
inputRef?: React.Ref<any> | React.RefObject<any>;
margin?: 'dense';
multiline?: boolean;
name?: string;
Expand Down
16 changes: 13 additions & 3 deletions packages/material-ui/src/Input/Input.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,10 +323,20 @@ class Input extends React.Component {
handleRefInput = node => {
this.input = node;

let ref;

if (this.props.inputRef) {
this.props.inputRef(node);
ref = this.props.inputRef;
} else if (this.props.inputProps && this.props.inputProps.ref) {
this.props.inputProps.ref(node);
ref = this.props.inputProps.ref;
}

if (ref) {
if (typeof ref === 'function') {
ref(node);
} else {
ref.current = node;
}
}
};

Expand Down Expand Up @@ -541,7 +551,7 @@ Input.propTypes = {
/**
* Use that property to pass a ref callback to the native input component.
*/
inputRef: PropTypes.func,
inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
/**
* If `dense`, will adjust vertical spacing. This is normally obtained via context from
* FormControl.
Expand Down
14 changes: 14 additions & 0 deletions packages/material-ui/src/Input/Input.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -471,4 +471,18 @@ describe('<Input />', () => {
assert.strictEqual(wrapper.childAt(1).type(), InputAdornment);
});
});

describe('prop: inputRef', () => {
it('should be able to return the input node via a ref object', () => {
const ref = React.createRef();
mount(<Input inputRef={ref} />);
assert.strictEqual(ref.current.tagName, 'INPUT');
});

it('should be able to return the textarea node via a ref object', () => {
const ref = React.createRef();
mount(<Input multiline inputRef={ref} />);
assert.strictEqual(ref.current.tagName, 'TEXTAREA');
});
});
});
2 changes: 1 addition & 1 deletion packages/material-ui/src/Input/Textarea.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface TextareaProps
disabled?: boolean;
rows?: string | number;
rowsMax?: string | number;
textareaRef?: React.Ref<any>;
textareaRef?: React.Ref<any> | React.RefObject<any>;
value?: string;
}

Expand Down
12 changes: 9 additions & 3 deletions packages/material-ui/src/Input/Textarea.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,14 @@ class Textarea extends React.Component {

handleRefInput = node => {
this.input = node;
if (this.props.textareaRef) {
this.props.textareaRef(node);

const { textareaRef } = this.props;
if (textareaRef) {
if (typeof textareaRef === 'function') {
textareaRef(node);
} else {
textareaRef.current = node;
}
}
};

Expand Down Expand Up @@ -224,7 +230,7 @@ Textarea.propTypes = {
/**
* Use that property to pass a ref callback to the native textarea element.
*/
textareaRef: PropTypes.func,
textareaRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
/**
* @ignore
*/
Expand Down
10 changes: 8 additions & 2 deletions packages/material-ui/src/Input/Textarea.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// @flow

import React from 'react';
import { assert } from 'chai';
import { spy, useFakeTimers } from 'sinon';
Expand Down Expand Up @@ -156,4 +154,12 @@ describe('<Textarea />', () => {
assert.strictEqual(wrapper.state().height, 43);
});
});

describe('prop: textareaRef', () => {
it('should be able to return the input node via a ref object', () => {
const ref = React.createRef();
mount(<Textarea textareaRef={ref} />);
assert.strictEqual(ref.current.tagName, 'TEXTAREA');
});
});
});
2 changes: 1 addition & 1 deletion packages/material-ui/src/NativeSelect/NativeSelectInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ NativeSelectInput.propTypes = {
/**
* Use that property to pass a ref callback to the native select element.
*/
inputRef: PropTypes.func,
inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
/**
* Name attribute of the `select` or hidden `input` element.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/material-ui/src/Radio/Radio.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Radio.propTypes = {
/**
* Use that property to pass a ref callback to the native input component.
*/
inputRef: PropTypes.func,
inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
/**
* Callback fired when the state is changed.
*
Expand Down
20 changes: 14 additions & 6 deletions packages/material-ui/src/Select/SelectInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,16 +142,24 @@ class SelectInput extends React.Component {
this.updateDisplayWidth();
};

handleSelectRef = node => {
if (!this.props.inputRef) {
handleInputRef = node => {
const { inputRef } = this.props;

if (!inputRef) {
return;
}

this.props.inputRef({
const nodeProxy = {
node,
// By pass the native input as we expose a rich object (array).
value: this.props.value,
});
};

if (typeof inputRef === 'function') {
inputRef(nodeProxy);
} else {
inputRef.current = nodeProxy;
}
};

render() {
Expand Down Expand Up @@ -276,7 +284,7 @@ class SelectInput extends React.Component {
value={Array.isArray(value) ? value.join(',') : value}
name={name}
readOnly={readOnly}
ref={this.handleSelectRef}
ref={this.handleInputRef}
type={type}
{...other}
/>
Expand Down Expand Up @@ -345,7 +353,7 @@ SelectInput.propTypes = {
/**
* Use that property to pass a ref callback to the native select element.
*/
inputRef: PropTypes.func,
inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
/**
* Properties applied to the `Menu` element.
*/
Expand Down
8 changes: 8 additions & 0 deletions packages/material-ui/src/Select/SelectInput.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,4 +327,12 @@ describe('<SelectInput />', () => {
});
});
});

describe('prop: inputRef', () => {
it('should be able to return the input node via a ref object', () => {
const ref = React.createRef();
mount(<SelectInput {...defaultProps} inputRef={ref} />);
assert.strictEqual(ref.current.node.tagName, 'INPUT');
});
});
});
2 changes: 1 addition & 1 deletion packages/material-ui/src/Switch/Switch.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Switch.propTypes = {
/**
* Use that property to pass a ref callback to the native input component.
*/
inputRef: PropTypes.func,
inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
/**
* Callback fired when the state is changed.
*
Expand Down
2 changes: 1 addition & 1 deletion packages/material-ui/src/TextField/TextField.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface TextFieldProps
InputLabelProps?: Partial<InputLabelProps>;
InputProps?: Partial<InputProps>;
inputProps?: InputProps['inputProps'];
inputRef?: React.Ref<any>;
inputRef?: React.Ref<any> | React.RefObject<any>;
label?: React.ReactNode;
margin?: PropTypes.Margin;
multiline?: boolean;
Expand Down
2 changes: 1 addition & 1 deletion packages/material-ui/src/TextField/TextField.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ TextField.propTypes = {
/**
* Use that property to pass a ref callback to the native input component.
*/
inputRef: PropTypes.func,
inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
/**
* The label content.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/material-ui/src/internal/SwitchBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ SwitchBase.propTypes = {
/**
* Use that property to pass a ref callback to the native input component.
*/
inputRef: PropTypes.func,
inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
/*
* @ignore
*/
Expand Down
2 changes: 1 addition & 1 deletion pages/api/checkbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ filename: /packages/material-ui/src/Checkbox/Checkbox.js
| <span class="prop-name">indeterminate</span> | <span class="prop-type">bool | <span class="prop-default">false</span> | If `true`, the component appears indeterminate. |
| <span class="prop-name">indeterminateIcon</span> | <span class="prop-type">node | <span class="prop-default">&lt;IndeterminateCheckBoxIcon /></span> | The icon to display when the component is indeterminate. |
| <span class="prop-name">inputProps</span> | <span class="prop-type">object |   | Properties applied to the `input` element. |
| <span class="prop-name">inputRef</span> | <span class="prop-type">func |   | Use that property to pass a ref callback to the native input component. |
| <span class="prop-name">inputRef</span> | <span class="prop-type">union:&nbsp;func&nbsp;&#124;<br>&nbsp;object<br> |   | Use that property to pass a ref callback to the native input component. |
| <span class="prop-name">onChange</span> | <span class="prop-type">func |   | Callback fired when the state is changed.<br><br>**Signature:**<br>`function(event: object, checked: boolean) => void`<br>*event:* The event source of the callback. You can pull out the new value by accessing `event.target.checked`.<br>*checked:* The `checked` value of the switch |
| <span class="prop-name">type</span> | <span class="prop-type">string |   | The input component property `type`. |
| <span class="prop-name">value</span> | <span class="prop-type">string |   | The value of the component. |
Expand Down
2 changes: 1 addition & 1 deletion pages/api/form-control-label.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Use this component if you want to display an extra label.
| <span class="prop-name">classes</span> | <span class="prop-type">object |   | Override or extend the styles applied to the component. See [CSS API](#css-api) below for more details. |
| <span class="prop-name">control</span> | <span class="prop-type">element |   | A control element. For instance, it can be be a `Radio`, a `Switch` or a `Checkbox`. |
| <span class="prop-name">disabled</span> | <span class="prop-type">bool |   | If `true`, the control will be disabled. |
| <span class="prop-name">inputRef</span> | <span class="prop-type">func |   | Use that property to pass a ref callback to the native input component. |
| <span class="prop-name">inputRef</span> | <span class="prop-type">union:&nbsp;func&nbsp;&#124;<br>&nbsp;object<br> |   | Use that property to pass a ref callback to the native input component. |
| <span class="prop-name">label</span> | <span class="prop-type">node |   | The text to be used in an enclosing label element. |
| <span class="prop-name">name</span> | <span class="prop-type">string |   | |
| <span class="prop-name">onChange</span> | <span class="prop-type">func |   | Callback fired when the state is changed.<br><br>**Signature:**<br>`function(event: object, checked: boolean) => void`<br>*event:* The event source of the callback. You can pull out the new value by accessing `event.target.checked`.<br>*checked:* The `checked` value of the switch |
Expand Down
2 changes: 1 addition & 1 deletion pages/api/input.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ filename: /packages/material-ui/src/Input/Input.js
| <span class="prop-name">id</span> | <span class="prop-type">string |   | The id of the `input` element. |
| <span class="prop-name">inputComponent</span> | <span class="prop-type">union:&nbsp;string&nbsp;&#124;<br>&nbsp;func<br> |   | The component used for the native input. Either a string to use a DOM element or a component. |
| <span class="prop-name">inputProps</span> | <span class="prop-type">object |   | Attributes applied to the `input` element. |
| <span class="prop-name">inputRef</span> | <span class="prop-type">func |   | Use that property to pass a ref callback to the native input component. |
| <span class="prop-name">inputRef</span> | <span class="prop-type">union:&nbsp;func&nbsp;&#124;<br>&nbsp;object<br> |   | Use that property to pass a ref callback to the native input component. |
| <span class="prop-name">margin</span> | <span class="prop-type">enum:&nbsp;'dense'&nbsp;&#124;<br>&nbsp;'none'<br> |   | If `dense`, will adjust vertical spacing. This is normally obtained via context from FormControl. |
| <span class="prop-name">multiline</span> | <span class="prop-type">bool | <span class="prop-default">false</span> | If `true`, a textarea element will be rendered. |
| <span class="prop-name">name</span> | <span class="prop-type">string |   | Name attribute of the `input` element. |
Expand Down
2 changes: 1 addition & 1 deletion pages/api/radio.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ filename: /packages/material-ui/src/Radio/Radio.js
| <span class="prop-name">icon</span> | <span class="prop-type">node |   | The icon to display when the component is unchecked. |
| <span class="prop-name">id</span> | <span class="prop-type">string |   | The id of the `input` element. |
| <span class="prop-name">inputProps</span> | <span class="prop-type">object |   | Attributes applied to the `input` element. |
| <span class="prop-name">inputRef</span> | <span class="prop-type">func |   | Use that property to pass a ref callback to the native input component. |
| <span class="prop-name">inputRef</span> | <span class="prop-type">union:&nbsp;func&nbsp;&#124;<br>&nbsp;object<br> |   | Use that property to pass a ref callback to the native input component. |
| <span class="prop-name">onChange</span> | <span class="prop-type">func |   | Callback fired when the state is changed.<br><br>**Signature:**<br>`function(event: object, checked: boolean) => void`<br>*event:* The event source of the callback. You can pull out the new value by accessing `event.target.value`.<br>*checked:* The `checked` value of the switch |
| <span class="prop-name">type</span> | <span class="prop-type">string |   | The input component property `type`. |
| <span class="prop-name">value</span> | <span class="prop-type">string |   | The value of the component. |
Expand Down
2 changes: 1 addition & 1 deletion pages/api/switch-base.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ filename: /packages/material-ui/src/internal/SwitchBase.js
| <span class="prop-name">indeterminate</span> | <span class="prop-type">bool |   | If `true`, the component appears indeterminate. |
| <span class="prop-name">indeterminateIcon</span> | <span class="prop-type">node |   | The icon to display when the component is indeterminate. |
| <span class="prop-name">inputProps</span> | <span class="prop-type">object |   | Attributes applied to the `input` element. |
| <span class="prop-name">inputRef</span> | <span class="prop-type">func |   | Use that property to pass a ref callback to the native input component. |
| <span class="prop-name">inputRef</span> | <span class="prop-type">union:&nbsp;func&nbsp;&#124;<br>&nbsp;object<br> |   | Use that property to pass a ref callback to the native input component. |
| <span class="prop-name">name</span> | <span class="prop-type">string |   | |
| <span class="prop-name">onChange</span> | <span class="prop-type">func |   | Callback fired when the state is changed.<br><br>**Signature:**<br>`function(event: object, checked: boolean) => void`<br>*event:* The event source of the callback. You can pull out the new value by accessing `event.target.checked`.<br>*checked:* The `checked` value of the switch |
| <span class="prop-name">type</span> | <span class="prop-type">string | <span class="prop-default">'checkbox'</span> | The input component property `type`. |
Expand Down
2 changes: 1 addition & 1 deletion pages/api/switch.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ filename: /packages/material-ui/src/Switch/Switch.js
| <span class="prop-name">icon</span> | <span class="prop-type">node |   | The icon to display when the component is unchecked. |
| <span class="prop-name">id</span> | <span class="prop-type">string |   | The id of the `input` element. |
| <span class="prop-name">inputProps</span> | <span class="prop-type">object |   | Attributes applied to the `input` element. |
| <span class="prop-name">inputRef</span> | <span class="prop-type">func |   | Use that property to pass a ref callback to the native input component. |
| <span class="prop-name">inputRef</span> | <span class="prop-type">union:&nbsp;func&nbsp;&#124;<br>&nbsp;object<br> |   | Use that property to pass a ref callback to the native input component. |
| <span class="prop-name">onChange</span> | <span class="prop-type">func |   | Callback fired when the state is changed.<br><br>**Signature:**<br>`function(event: object, checked: boolean) => void`<br>*event:* The event source of the callback. You can pull out the new value by accessing `event.target.checked`.<br>*checked:* The `checked` value of the switch |
| <span class="prop-name">type</span> | <span class="prop-type">string |   | The input component property `type`. |
| <span class="prop-name">value</span> | <span class="prop-type">string |   | The value of the component. |
Expand Down
2 changes: 1 addition & 1 deletion pages/api/text-field.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ For advanced cases, please look at the source of TextField by clicking on the
| <span class="prop-name">InputLabelProps</span> | <span class="prop-type">object |   | Properties applied to the `InputLabel` element. |
| <span class="prop-name">InputProps</span> | <span class="prop-type">object |   | Properties applied to the `Input` element. |
| <span class="prop-name">inputProps</span> | <span class="prop-type">object |   | Attributes applied to the native `input` element. |
| <span class="prop-name">inputRef</span> | <span class="prop-type">func |   | Use that property to pass a ref callback to the native input component. |
| <span class="prop-name">inputRef</span> | <span class="prop-type">union:&nbsp;func&nbsp;&#124;<br>&nbsp;object<br> |   | Use that property to pass a ref callback to the native input component. |
| <span class="prop-name">label</span> | <span class="prop-type">node |   | The label content. |
| <span class="prop-name">margin</span> | <span class="prop-type">enum:&nbsp;'none'&nbsp;&#124;<br>&nbsp;'dense'&nbsp;&#124;<br>&nbsp;'normal'<br> |   | If `dense` or `normal`, will adjust vertical spacing of this and contained components. |
| <span class="prop-name">multiline</span> | <span class="prop-type">bool |   | If `true`, a textarea element will be rendered instead of an input. |
Expand Down

0 comments on commit 0b06244

Please sign in to comment.