2

My code below works but the this.buttonRef.current.firstChild.focus() stops working if it's not in a setTimeout function.

From looking at the official docs for refs I cant see why this is happening. Is there anything obviously wrong with my component? If not Im wondering if another component on my site is 'stealing' focus as when the url prop changes a modal is closed.

UPDATE: One weird thing is if I console.log outside of the setTimeout then I can see the element is present in the DOM.

UPDATE2: Turns out it was React Trap Focus in my modal that was causing the issue. Removing the focus trap means I don't need the timeout. As I need the focus trap I think the setTimeout will need to stay.

https://github.com/davidtheclark/focus-trap-react

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.buttonRef = React.createRef();
  }

  componentDidUpdate(prevProps) {
    if (this.props.url === '' && prevProps.url = "old-url") {
      console.log('target element: ', this.buttonRef.current.firstChild)

      // This doenst work if not in a setTimeout
      // this.buttonRef.current.firstChild.focus();
      setTimeout(() => {
        this.buttonRef.current.firstChild.focus();
      }, 1);
    }
  }

  render() {
    const {
      limitIsReached,
        add
    } = this.props;

    return (
      <Fragment>
        <Title>My title</Title>
        <Section>
          <Button>
            Add a promo code
          </Button>
          <span ref={this.buttonRef}>
            {limitIsReached ? (
              <Alert
                message="Sorry limit reached"
              />
            ) : (
              <Button
                onClick={add}
              >
                Add new
              </Button>
            )}
          </span>

          <List compact />
        </Section>
      </Fragment>
    );
  }
}

export default MyComponent;
Evanss
  • 23,390
  • 94
  • 282
  • 505
  • Have you [seen this issue](https://stackoverflow.com/questions/44074747/componentdidmount-called-before-ref-callback)? – Tholle Mar 14 '19 at 08:33
  • @Tholle Ive updated my question. Yes I have but as console.log works I don't think I'm seeing the same issue. – Evanss Mar 14 '19 at 08:43

1 Answers1

1

Considering that seemingly componentDidUpdate runs before your buttonRef is resolved, a short setTimeout isn't the worst solution.

You could try other ways involving setting state:

componentDidUpdate(prevProps) {
  if (.... oldurl) {
    this.setState({focusBtn: true})
  }

Then when the buttonref resolves:

<span ref={ref=>{
  if (this.state.focusBtn) {
    this.buttonRef = ref;
    this.buttonRef.current.firstChild.focus();
  } } >...

EDIT

ok so if you remove the conditional in your render method, React will ensure that your ref has resolved on componentDidMount and also componentDidUpdate (as you wish it to be)

Try this:

<span ref={this.buttonRef}>

          <Alert
            message="Sorry limit reached"
            style={{display: limitIsReached ? 'block' : 'none'}}
          />

          <Button
            onClick={add} style={{display: limitIsReached ? 'none' : 'inline-block'}}
          >
            Add new
          </Button>
        )}
      </span>
jsdeveloper
  • 3,945
  • 1
  • 15
  • 14
  • I need logic in componentDidUpdate so that focus is only applied after a specific url change. You your code not apply the focus when the component was first rendered? – Evanss Mar 14 '19 at 08:44
  • did removing the conditional render help? – jsdeveloper Mar 14 '19 at 11:53