3

I'm trying to focus() a conditionally rendered textarea in React. The code below is almost exactly identical to the example in the React docs or to this similar question.

The code below immediately shows and focuses the textarea. If the three commented lines are uncommented, the textarea gets shown after the condition prop is set to true (its value depends on the state of the parent and is initially false), but the element doesn't get focused anymore.

If the condition is initially true, the input element gets focused as expected when the component renders for the first time. The problem occurs when the condition is changed from false to true.

import React, { Component } from 'react'

class TestClass extends Component {
  constructor(props) {
    super(props)
    this.focus = this.focus.bind(this)
  }
  componentDidMount() {
    // this.props.condition &&
    this.focus()
  }
  componentDidUpdate() {
    // this.props.condition &&
    this.focus()
  }

  focus() {
    console.log(`this.textInput: ${this.textInput}`)
    this.textInput.focus()
  }

  render() {
    return (
      <div>
        {
          // this.props.condition &&
          <textarea
            ref={(input) => {this.textInput = input}}
            defaultValue="Thanks in advance for your invaluable advice">
            {console.log('textarea rendered')}
          </textarea>
        }
      </div>
    )
  }
}

The console output

textarea rendered
this.textInput: [object HTMLTextAreaElement]

rules out that the element is unavailable at the time focus() gets executed.

Furthermore:

  • Setting autoFocus attribute doesn't seem to work, in contrast to this question
  • Same problem for both <input /> and <textarea />

Edit: in response to the question below, the parent component looks as follows.

class ParentComponent extends Component {
  constructor(props) {
  super(props)
    this.state = {
      condition: false
    }
    this.toggleCondition = this.toggleCondition.bind(this)
  }
  toggleCondition() {
    this.setState(prevState => ({
      condition: !prevState.condition
    }))
  }
  render() {
    return (
      <div>
        <TestClass condition={this.state.condition} />
        <button onMouseDown={this.toggleCondition} />
      </div>
    )
  }
}
Bart
  • 1,600
  • 1
  • 13
  • 29
  • If you substitute your `this.props.condition &&` for a more verbose `if(this.props.condition) { }` in both componentDidMount and componentDidUpdate does this change the behaviour in any way? – Finbarr O'B Jul 24 '17 at 09:21
  • Unfortunately, no. In JavaScript, `true && expression` always evaluates to `expression`, and `false && expression` always evaluates to `false`. (Source: https://facebook.github.io/react/docs/conditional-rendering.html#inline-if-with-logical--operator) – Bart Jul 24 '17 at 09:25
  • Can you add your parent component code which is adding the TestClass component? I think the problem may lie in how you are passing 'condition' as a prop, if I pass it as a string using ``, it doesn't work, however passing condition as a boolean flag works for me: `` – Finbarr O'B Jul 24 '17 at 09:36
  • My guess is that the problem lies with the condition being initially `false`. If I set the condition to be initially `true`, it works. The condition is a boolean, but I will post the parent class in a minute. – Bart Jul 24 '17 at 09:40
  • I've tested your code in playground. `TestClass` updates properly as you expect. `this.focus` executes on every toggling. Maybe I got something wrong and didn't get the question. Can you provides some other details? – Slowyn Jul 24 '17 at 10:22
  • I'm seeing the same (as evidenced by the console output): `TestClass` updates and `this.focus` executes. What I don't see however is the textarea getting focus (with blue border / blinking caret) in Safari, Chrome or Firefox. – Bart Jul 24 '17 at 10:26
  • It's still strange because I see focusing in Chrome/Safari. – Slowyn Jul 24 '17 at 10:35
  • Is your condition initially `true`? The problem only manifests itself when the condition is changed from `false` to `true`. I've updated the question to make this more clear. – Bart Jul 24 '17 at 10:37
  • Nah, everything works properly in both cases. Anyway your code looks good, so it's really very weird that code doesn't work fine. – Slowyn Jul 24 '17 at 10:41
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/149981/discussion-between-konstantin-yakushin-and-bart). – Slowyn Jul 24 '17 at 10:51

2 Answers2

3
import React, { Component } from 'react';

class TestClass extends Component {
  constructor(props) {
    super(props);
    this.focus = this.focus.bind(this);
  }
  componentDidMount() {
    this.focus();
  }
  componentWillReceiveProps(nextProps) {
    if (nextProps.condition !== this.props.condition) {
        this.focus();  
    }
  }
  focus() {
    console.log(`this.textInput: ${this.textInput}`);
    if (this.props.condition === true) {
      this.textInput.focus();
    }
  }

  render() {
    return (
      <div>
        {
          this.props.condition &&
          <textarea
            ref={(input) => { this.textInput = input; }}
            defaultValue="Thanks in advance for your invaluable advice"
          >
            {console.log('textarea rendered')}
          </textarea>
        }
      </div>
    );
  }
}
export default TestClass;
Dinesh Katwal
  • 930
  • 1
  • 14
  • 25
1

Turns out the problem was really really stupid. The way I implemented the toggle button (which I simplified to <button onClick={this.toggleCondition} /> in the original question "for clarity"), was with a custom component which takes the onClick prop and attaches its value to the onMouseDown attribute of a hyperlink.

Because the hyperlink gets focused after its onMouseDown action, the focus was immediately taken away from the textarea.

I've edited the question to reflect my usage of onMouseDown.

Bart
  • 1,600
  • 1
  • 13
  • 29