5

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'));
Garrett Kadillak
  • 1,026
  • 9
  • 18
ofhouse
  • 3,047
  • 1
  • 36
  • 42

1 Answers1

6

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.

Marco Scabbiolo
  • 7,203
  • 3
  • 38
  • 46
  • 1
    Wow, thank you so much for taking the time to point out everything in detail! Your approach feels much cleaner than mine, really learned something here. I've tested it out on my code and it works really well, definitely saved me some hours. Thank You! – ofhouse Jun 05 '17 at 09:40