Skip to content

Commit

Permalink
fix(text-field): added reference to input element (#414)
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Goo authored Nov 6, 2018
1 parent 90adbbe commit ea04dc6
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 11 deletions.
14 changes: 10 additions & 4 deletions packages/text-field/Input.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import classnames from 'classnames';
import {VALIDATION_ATTR_WHITELIST} from '@material/textfield/constants';

export default class Input extends React.Component {
inputElement = React.createRef();
inputElement_ = React.createRef();
state = {wasUserTriggeredChange: false};

componentDidMount() {
Expand Down Expand Up @@ -85,6 +85,11 @@ export default class Input extends React.Component {
return classnames('mdc-text-field__input', this.props.className);
}

get inputElement() {
const element = this.inputElement_.current;
return element ? element : null;
}

handleFocus = (e) => {
const {foundation, handleFocusChange, onFocus} = this.props;
foundation.activateFocus();
Expand Down Expand Up @@ -141,12 +146,12 @@ export default class Input extends React.Component {
});
}

isBadInput = () => this.inputElement.current.validity.badInput;
isBadInput = () => this.inputElement_.current.validity.badInput;
isValid = () => {
if (this.props.isValid !== undefined) {
return this.props.isValid;
}
return this.inputElement.current.validity.valid;
return this.inputElement_.current.validity.valid;
}

render() {
Expand All @@ -170,6 +175,7 @@ export default class Input extends React.Component {
/* eslint-enable no-unused-vars */
...otherProps
} = this.props;

const InputComponent = inputType;
return (
<InputComponent
Expand All @@ -180,7 +186,7 @@ export default class Input extends React.Component {
onChange={this.handleChange}
disabled={disabled}
value={value}
ref={this.inputElement}
ref={this.inputElement_}
className={this.classes}
{...otherProps}
/>
Expand Down
40 changes: 40 additions & 0 deletions packages/text-field/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,49 @@ setDisabled | Function | Callback function that is called when the `disabled` pr
setInputId | Function | Callback function that is called when the `id` attribute updates.
handleFocusChange | Function | Callback function that is called when `focus` or `blur` events occur
value | Number/String | Value of the input.
ref | Function(input: ReactElement) => void | On mount of component, will call passed function with the instance of the `<Input />`.

>NOTE: the `<Input>` component will receive all properties that a standard `<input>` accepts.
### Accessing the Native Input element

There will be times when you need to access the native <input />. For example if you need to focus the text field, you can add a ref callback method to the `<Input />` element and access the `<input />`. The `ref` will accept a callback method and on mount and will pass the instance of the input component. Here is an example of how to programatically focus the `<input />`:

```js
import React from 'react';
import TextField, {Input} from '@material/react-text-field';
import Button from '@material/react-button';

class MyApp extends React.Component {
input = null;
state = {value: 'Woof'};

focusTextField = () => {
if (!this.input) return;
const inputElement = this.input.inputElement;
if (inputElement) {
inputElement.focus();
}
}

render() {
return (
<div>
<div>
<Button onClick={this.focusTextField}>Focus Text Field</Button>
</div>
<TextField label='Dog'>
<Input
value={this.state.value}
ref={input => this.input = input}
onChange={(e) => this.setState({value: e.target.value})}/>
</TextField>
</div>
);
}
}
```


### Sass Mixins

Expand Down
20 changes: 13 additions & 7 deletions packages/text-field/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class TextField extends React.Component {
constructor(props) {
super(props);
this.floatingLabelElement = React.createRef();
this.inputElement_ = React.createRef();
this.inputComponent_ = null;

this.state = {
// root state
Expand Down Expand Up @@ -163,9 +163,9 @@ class TextField extends React.Component {
getNativeInput: () => {
let badInput;
let valid;
if (this.inputElement_ && this.inputElement_.current) {
badInput = this.inputElement_.current.isBadInput();
valid = this.inputElement_.current.isValid();
if (this.inputComponent_) {
badInput = this.inputComponent_.isBadInput();
valid = this.inputComponent_.isValid();
}
const input = {
validity: {badInput, valid},
Expand Down Expand Up @@ -223,14 +223,20 @@ class TextField extends React.Component {
};
}

inputProps(props) {
inputProps(child) {
const {props, ref} = child;
return Object.assign({}, props, {
foundation: this.state.foundation,
handleFocusChange: (isFocused) => this.setState({isFocused}),
handleValueChange: (value, cb) => this.setState({value}, cb),
setDisabled: (disabled) => this.setState({disabled}),
setInputId: (id) => this.setState({inputId: id}),
ref: this.inputElement_,
ref: (input) => {
if (typeof ref === 'function') {
ref(input);
}
this.inputComponent_ = input;
},
inputType: this.props.textarea ? 'textarea' : 'input',
});
}
Expand Down Expand Up @@ -277,7 +283,7 @@ class TextField extends React.Component {

renderInput() {
const child = React.Children.only(this.props.children);
const props = this.inputProps(child.props);
const props = this.inputProps(child);
return React.cloneElement(child, props);
}

Expand Down
7 changes: 7 additions & 0 deletions test/unit/text-field/Input.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,3 +298,10 @@ test('#event.onChange calls props.onChange()', () => {
wrapper.simulate('change', event);
td.verify(onChange(event), {times: 1});
});

test('#inputElement should return the native input', () => {
const wrapper = mount(<Input />);
const inputElement = wrapper.instance().inputElement;
assert.equal(inputElement.tagName.toLowerCase(), 'input');
assert.isTrue(inputElement instanceof HTMLInputElement);
});
8 changes: 8 additions & 0 deletions test/unit/text-field/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,14 @@ test('#inputProps.setInputId updates state.disabled', () => {
assert.equal(wrapper.state().inputId, 'my-id');
});

test('passing a ref to the <Input /> should return the instance of the Input', () => {
let inputInstance = null;
const wrapper = mount(<TextField label='my label'>
<Input ref={(input) => inputInstance = input}/>
</TextField>);
assert.equal(wrapper.childAt(0).childAt(0).instance(), inputInstance);
});

test('#componentWillUnmount destroys foundation', () => {
const wrapper = shallow(<TextField label='my label'><Input /></TextField>);
const foundation = wrapper.state().foundation;
Expand Down

0 comments on commit ea04dc6

Please sign in to comment.