Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Focus on newly-rendered text input after tab-fired blur event

I have a React (15.3.2) component with a text input.

(Everywhere I say "render" here it's actually render or unhide; I've tried both.)

When that input element is blurred, I render a new component with a text input.

I want give the new text input focus.

I've tried componentDidMount, componentWillUpdate, and componentDidUpdate; I've tried named and function refs; I've tried react-dom.

The focusing itself works, e.g., once it's been rendered, if I click in the initial input, focus goes to the new input (this is a bug, but compared to focusing, trivial).

The first input has an onBlur that sets the state used to tell the second input to render or not.

In that blur handler I stop the event as best as I can.

When I tab out of the first element I'm already "past" the newly-rendered element, e.g., the browser tab bar in my current bare design–I guess the new element hasn't been rendered yet?

class SecondInput extends Component {
  componentDidUpdate = (prevProps, prevState) => {
    if (!this.props.hidden) this._input.focus()
  }

  render = () => 
    <input type="text" hidden={this.props.hidden} ref={(c) => this._input = c}
}

class NewItem extends Component {
  state = { itemEntered: false }

  itemBlurred = (e) => {
    e.preventDefault()
    e.stopPropagation()
    this.setState({ itemEntered: true })
  }

  render = () =>
    <div>
      Item: <input type="text" onBlur={this.itemBlurred} />
      <SecondInput hidden={!this.state.itemEntered} />
    </div>
}

Any ideas or hints? I have to believe it's something obvious, because surely this happens all the time.

I'm also open to any other form of component hierarchy, e.g., if I need to have a container that wraps all this stuff up somehow that's fine.

React 15.3.2

like image 471
Dave Newton Avatar asked Sep 06 '25 14:09

Dave Newton


1 Answers

The problem you are seeing appears to be because there are no more focusable elements on the page when you press tab, so the focus goes to the address bar. For some reason when the focus is on the address bar, just calling this._input.focus() does not grab focus as you would expect.

In order to combat this problem, I have added an empty div, and set the tabIndex property based on whether or not the second input is shown.

Just to make things easier for myself, I made the input focus on mount instead of using the hidden property. This may or may not work in your case, but it seemed to be cleaner since it would keep the input from calling focus on every keypress if it were to be a controlled input.

let Component = React.Component;


class SecondInput extends Component {
  componentDidMount(){
    this.textInput.focus()
  }

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

class NewItem extends Component {
  state = { itemEntered: false }

  itemBlurred = (e) => {
    //e.preventDefault()
    //e.stopPropagation()
    this.setState({ itemEntered: true })
  }

  render = () =>
    <div>
      Item: <input type="text" onBlur={this.itemBlurred} />
      {
        this.state.itemEntered ? [
          <SecondInput/>
        ] : []
       }
       <div tabIndex={this.state.itemEntered ? null :  0}/>
    </div>
}

ReactDOM.render(<NewItem />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.0/react-dom.min.js"></script>
like image 116
Mobius Avatar answered Sep 09 '25 04:09

Mobius