Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
This is a workaround for the buggy react-native TextInput multiline on Android. Inspired by the comments on https://github.com/facebook/react-native/issues/12717.
import React, {PropTypes, PureComponent} from 'react';
import {TextInput} from 'react-native';
import debounce from 'debounce';
/**
* This is a workaround for the buggy react-native TextInput multiline on Android.
*
* Can be removed once https://github.com/facebook/react-native/issues/12717
* is fixed.
*
* Example for usage:
* <MultilineTextInput value={this.state.text} onChangeText={text => setState({text})} />
*/
export default class MultilineTextInput extends PureComponent {
constructor(props) {
super(props);
this.state = {selection: {start: 0, end: 0}};
// Prevent 2 newlines for some Android versions, because they dispatch onSubmitEditing twice
this.onSubmitEditing = debounce(this.onSubmitEditing.bind(this), 100, true);
}
onSubmitEditing() {
const {selection} = this.state;
const {value} = this.props;
const newText = `${value.slice(0, selection.start)}\n${value.slice(selection.end)}`;
// move cursor only for this case, because in other cases a change of the selection is not allowed by Android
if (selection.start !== this.props.value.length && selection.start === selection.end) {
this.setState({
selection: {
start: selection.start + 1,
end: selection.end + 1,
},
});
}
this.props.onChangeText(newText);
}
render() {
return (
<TextInput
multiline
blurOnSubmit={false}
selection={this.state.selection}
value={this.props.value}
onSelectionChange={event => this.setState({selection: event.nativeEvent.selection})}
onChangeText={this.props.onChangeText}
onSubmitEditing={this.onSubmitEditing}
{...this.props}
/>
);
}
}
MultilineTextInput.propTypes = {
value: PropTypes.string.isRequired,
onChangeText: PropTypes.func.isRequired,
};
////////////////// Tests //////////////////
import 'react-native';
// Require after react-native
import renderer from 'react-test-renderer';
import React from 'react';
import {shallow} from 'enzyme';
import MultilineTextInput from '../MultilineTextInput';
describe('MultilineTextInput', () => {
const text = 'some value';
const onChangeText = jest.fn();
const defaultProps = {
value: text,
onChangeText,
};
it('renders correctly', () => {
const tree = renderer.create(<MultilineTextInput {...defaultProps} />).toJSON();
expect(tree).toMatchSnapshot();
});
it('inserts a new line at the end if the "enter" soft key is pressed', () => {
const component = shallow(<MultilineTextInput {...defaultProps} />);
component.simulate('selectionChange', {nativeEvent: {selection: {start: text.length, end: text.length}}});
component.simulate('submitEditing');
expect(onChangeText).toBeCalledWith('some value\n');
});
it('inserts a new line in the middle if the cursor is in the middle and the "enter" soft key is pressed', () => {
const component = shallow(<MultilineTextInput {...defaultProps} />);
component.simulate('selectionChange', {nativeEvent: {selection: {start: 5, end: 5}}});
component.simulate('submitEditing');
expect(onChangeText).toBeCalledWith('some \nvalue');
});
it('inserts a new line in the middle if text is selected and the "enter" soft key is pressed', () => {
const component = shallow(<MultilineTextInput {...defaultProps} />);
component.simulate('selectionChange', {nativeEvent: {selection: {start: 4, end: 7}}});
component.simulate('submitEditing');
expect(onChangeText).toBeCalledWith('some\nlue');
});
});
@zainozzaini

This comment has been minimized.

Copy link

commented Jul 5, 2017

I got an error "undefined is not an object (ecaluating 'value.slice')" when enter to new line.

@kohei-takata

This comment has been minimized.

Copy link

commented Sep 4, 2017

We use this greate component in our app.
Thanks @catchin !

@codewithpassion

This comment has been minimized.

Copy link

commented Sep 6, 2017

Hey, thanks for this work!
I ran into a little issue with it when I'm using it while handling selection changes myself.

To fix those I had to add:

componentWillReceiveProps(props) {
    if (props.selection) {
        this.setState({ selection: props.selection });
    }
}
focus() {
    this.refs.text.focus();
}

And change onSubmitEditting to:

onSubmitEditing() {
    const { selection } = this.state;
    const { value } = this.props;
    const newText = `${value.slice(0, selection.start)}\n${value.slice(selection.end)}`;
    
    this.props.onChangeText(newText);
    
    // move cursor only for this case, because in other cases a change of the selection is not allowed by Android
    if (selection.start !== this.props.value.length && selection.start === selection.end) {
        const newSelection = {
            selection: {
                start: selection.start + 1,
                end: selection.end + 1,
            },
        };
        if (this.props.onSelectionChange) {
            this.props.onSelectionChange({ nativeEvent: newSelection });
        } else {
            this.setState(newSelection);
        }
    }
}

Essentially, I'm allowing the container to manage the selection state themselves.

@codewithpassion

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.