Search code examples
javascriptreactjsdom-eventsreact-dom

Change focus of text input with react


My goal here is to create a form, which tabs you to the next input element when you hit the return key and submits the form when you are on the last input.

This is being built for mobile, and since there is no option to use the 'next' button instead of the 'go keyboard' button in browser (for more information about this see this answer).

To better visualize this, here is a picture:enter image description here

I've also written some code, but the point here is that I am not able to catch the event in the right place, so the form gets immediately submitted after the return or when I prevent the event, the focus is changed after I hit return 2 times.

See the example here: https://codepen.io/ofhouse/pen/Rgwzxy (I've also pasted the code below)

class TextInput extends React.Component {
  constructor(props) {
    super(props);
    this._onKeyPress = this._onKeyPress.bind(this);
  }

  componentDidMount() {
    if (this.props.focus) {
      this.textInput.focus();
    }
  }

  componentDidUpdate(nextProps) {
    if (nextProps.focus) {
      this.textInput.focus();
    }
  }

  _onKeyPress(e) {
    if (e.key === 'Enter') {
      this.props.onSubmit(e);
    }
  }

  render() {
    return (
      <div>
        <input
          type="text"
          onKeyPress={this._onKeyPress}
          ref={input => {
            this.textInput = input;
          }}
        />
      </div>
    );
  }
}

class Application extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      currentElement: 0,
    };
  }

  _submitForm(e) {
    // If I remove this preventDefault it works, but also the form is submitted --> SiteReload
    e.preventDefault();
  }

  _changeFocus(nextElement) {
    return e => {
      this.setState({
        currentElement: nextElement,
      });
    };
  }

  render() {
    const { currentElement } = this.state;
    return (
      <form>

        <h1>React input focus</h1>

        <TextInput focus={currentElement === 0} onSubmit={this._changeFocus(1)} />
        <TextInput focus={currentElement === 1} onSubmit={this._changeFocus(0)} />

        <div>
          <button type="submit" onClick={this._submitForm}>Submit</button>
        </div>

      </form>
    );
  }
}

ReactDOM.render(<Application />, document.getElementById('app'));

Solution

  • I don't think you're using the best approach, let me explain. The focusing of inputs is done by the focus method of the native DOM element. To know what input to focus based on which input has the current focus is a logic that has to be implemented in the container of those inputs, the Application component in your case.

    I've made some significant changes to your code and now it's working: CodePen

    Let me explain it:

    First of all, we're preventing the submit of the keyPressed event of the input when the key pressed is 'Enter', to prevent the form submit

    _onKeyPress(e) {
      if (e.key === 'Enter') {
        this.props.onSubmit(e);
        e.preventDefault();
      }
    }
    

    We don't need either componenDidMount nor componentDidUpdate in TextInput all we need is a focus method:

    focus() {
      this.textInput.focus();
    }
    

    Most of the changes are made in the Application component. First of all, we don't need the state, what we really need is to have the inputs in an array so we can call focus on them.

    constructor(props) {
      super(props);
    
      this.inputs = [];
    }
    

    To change the focus, we just need the index of the input to call the focus method of the TextInput component:

    _changeFocus(index) {
      return e => {
        this.inputs[index].focus();
      };
    }
    

    Then we need a way to add the inputs into this.inputs, the ref property would be ideal, I've create the method _addInput as a helper for this:

    _addInput(index) {
      return input => {
        this.inputs[index] = input;
      };
    }
    

    Finally, when rendering the TextInputs you need to pass them _addInput to ref and _changeFocus to onSubmit, with their respective indexes:

    <TextInput ref={this._addInput(0)} onSubmit={this._changeFocus(1)} />
    <TextInput ref={this._addInput(1)} onSubmit={this._changeFocus(0)} />
    

    In the second TextInput I'm changing the focus to the first one, but maybe submitting the form would be more useful.