Webbower Webbower - 7 months ago 18
Javascript Question

Programmatically focus on a form element after mounting

I have a form component (FormComponent) that I need to programmatically focus on when a sibling button component is clicked. I used to be able to call

this.refs.myForm.focus()
which (given
myForm
as FormComponent) was written to call
.focus()
on the first form field inside. However, I've refactored and abstracted some behavior into a higher order component that wraps FormComponent so now, the
.focus()
method on FormComponent is intercepted by the wrapper component.

Using React 0.14.x (so I don't need
ReactDOM.findDOMnode()
for DOM components):

// FormComponent
class FormComponentBase extends React.Component {
focus() {
this.refs.firstField.focus();
}

render() {
return (
<form ...>
<input ref="firstField" />
</form>
);
}
}

// Wraps FormComponent in a wrapper component with abstracted/reusable functionality
const FormComponent = hocWrapper(FormComponent);

// OtherComponent
class OtherComponent extends React.Component {
handleClick(ev) {
// ERROR This doesn't work because the hocWrapper component is intercepting the .focus() call
this.refs.myForm.focus();
}

render() {
return (
<div>
<button onClick={this.handleClick.bind(this)}>Focus</button>
<FormComponent ref="myForm" />
</div>
);
}
}


Passing a
focus
prop and managing the state of whether the form's first element is focused in
OtherComponent
seems ludicrous. I know you can create
get
/
set
properties using
defineProperty
(or something like that) where accessing an instance property calls a method under the hood to generate the result, but does JS have something like Ruby's
missingMethod
or PHP's
__call
where I can define a catch-all method on my
hocWrapper
that just passes method calls through to the wrapped component? I've run into this issue before with calling other methods through HOC wrapper components but I think I just defined a pass-thru method of the same name on the HOC wrapper (which I'm fairly certain is the wrong way).

Answer

Rather than passing focus state down, you can pass the ref up fairly easily with the ref callback function. Here's a fiddle illustrating

class FormComponentBase extends React.Component {
  render() {
    return (
      <form ...>
         <input ref={node => this.props.passNode(node)} />
      </form>
    );
  }
}

class OtherComponent extends React.Component {
  receiveNode(node) {
    if (!this.state.node) this.setState({ node })
  }

  render() {
    return (
      <div>
        <button onClick={() => this.state.node.focus()}>Focus</button>
        <FormComponent passNode={this.receiveNode} />
      </div>
    );
  }
}    

UPDATE: a more idiomatic way that doesn't pass the node up. updated fiddle

var FormComponentBase = React.createClass({
  render() {
    return <input ref={ node => 
      node && this.props.shouldFocus && node.focus()
    }/>
  }
})

var OtherComponent = React.createClass({
  getInitialState() {
    return { shouldFocus: false }
  },

  toggleFocus(node) {
    // toggle for demonstration
    this.setState({ shouldFocus: !this.state.shouldFocus })
  },

  render: function() {
    return (
      <div>
        <button onClick={() => this.toggleFocus() }>focus</button>
        <Child shouldFocus={this.state.shouldFocus} />
      </div>
    )
  }
});