Ad

Higher-Order Component And Accessing Child Of The Wrapped Element

Pretty new to React, and I have this issue where 2 of my components are fairly similar, but different enough to not be the same.

I looked up a way to compose the components and avoid duplications, and with Mixins out of the way, I encountered HOC, which I think can be pretty powerful, yet confusing for somebody like me who's not yet familiar with React and its inner workings.

So I have my main component(wrappee) which will then be wrapped. In turn that component renders a child component (in my case a textarea for one of the wrappee component, the other one being an input field).

One of the reasons why I'd like to use HOC is that I bind event listeners to the text input, and so need to be able to access it from the HOC.

    Textarea ---- InputController ---- InputHOC
    Input    ---/

In practice I have managed a way to communicate the DOM element back to the HOC, using a callback in ref. But I feel it's pretty dirty, as I have to have 2 callbacks:

InputController:

 return (
  <div>
    {textareaHint}
    <div className='longtext-input-wrapper'>
      {textareaComponent}
    </div>
  </div>
)

textareaComponent

return <TextareaAutosize
          ref = {
            (c) => {
              this._input = c
            }
          }
          className={classList}
          minRows={MIN_ROWS}
        />

and then in InputHOC:

 <div className={submitButtonClass}>
    <Button clickHandler={_buttonClickHandler.bind(this)} ref='submitButton'>
      <span className='button-text'>Ok</span>
      <span className='button-icon'>✔</span>
    </Button>
    <InputComponent ref={
        (item) => {
          if (item && !this.input) {
            this.input = item._input
          }
        }
      } 
    />
 </div>

And then I can access this.input in componentDidMount:

const inputNode = ReactDOM.findDOMNode(this.input)

It does really feel hacky, so I was wondering if there was a better way to deal with this?

Thanks a lot for your input

Ad

Answer

@Jordan is right, this is not using the HOC pattern. A good example of this pattern can be found here: https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775. You can think of a HOC as a superclass for the component that it wraps, but it's not actually a class. A HOC is a function that takes a react component and returns a new one with the desired enhancements.

What you are doing is using ref callbacks to link together all of your components and making other components aware of things other than their direct children, which is definitely not recommended. In your case, I don't see why you shouldn't place everything from InputHOC into InputController. Then, define the functions that you want to bind to the input in TextareaAutosize in InputController and pass them as props. It would look something like this:

InputController:

class InputController extends React.Component {
  handleChange() {
    // Note that 'this' will not be set to the InputController element
    // unless you bind it manually
    doSomething();
  }

  render() {
    return (
      <Button clickHandler={_buttonClickHandler.bind(this)} ref='submitButton'>
        <span className='button-text'>Ok</span>
        <span className='button-icon'>✔</span>
      </Button>
      <div>
        {textareaHint}
        <div className='longtext-input-wrapper'>
          <TextareaAutosize callback={this.handleChange} />
        </div>
      </div>
    );
  }
}

TextareaAutosize

class TextAreaAutosize extends React.Component {
  render() {
    return (
      <textarea onChange={this.props.onChange}>
        Some text value
      </textarea>
    )
  }
}
Ad
source: stackoverflow.com
Ad