Search code examples
javascriptreactjsref

How to use refs in React Higher-Order Component (HOC)


I have 3 nav items that have different markup but all need the same functionality -- to animate the svg paths within it in sequence. Sounds like a good scenario to use an HOC. However, I am unable to push the refs to an array via a function (activePaths) called from the ref in the WrappedComponent. I get an error TypeError: _this2.activePaths is not a function. Here's a working codepen of what I'm trying to accomplish for a single nav item without the using the HOC.

This is the code with the HOC that gives me the error.

export const withNavItem = WrappedComponent => class extends Component {
  constructor() {
    super();
    this.activePaths = this.activePaths.bind(this);
    this.markerPaths = [];
    this.rendered = false;
  }
  componentDidMount() {
    this.rendered = true;
    this.animatePaths();
  }

  animatePaths() {
    const { markerPaths } = this;
    // prepare stroke to be animated
    for (let i = 0; i < markerPaths.length; i++) {
      const path = markerPaths[i];
      const length = path.getTotalLength();
      path.setAttribute('strokeDasharray', length);
      path.style.strokeDashoffset = length;
    }
    // animate stroke
    const markerDrawing = anime({
      targets: markerPaths,
      strokeDashoffset: [anime.setDashoffset, 0],
      easing: 'easeInOutSine',
      duration: 400,
      delay(el, i) { return i * 150; },
      direction: 'alternate',
      loop: false,
    });
  }

  activePaths(el, linkType) {
    if (el === null || this.rendered) {
      return;
    }
    this.markerPaths.push(el);
  }

  render() {
    this.rendered = true;
    return <WrappedComponent {...this.props} />;
  }
};

export default withNavItem;




class NavItemHey extends React.Component {     
  render() {
    return (
      <div>
        <span className="letter-holder">
          <span className="letter">H</span>
          <span className="letter-strokes letter-strokes--h">
            <span className="h-left-stroke letter-stroke">
              <svg className="letter-stroke__svg" viewBox="0 0 106 306">
                <path ref={el => this.activePaths(el)} className="letter-stroke__svg-path" d="M59,0c0,0,0.1,114-0.5,195.8S58,314.5,58,314.5" />
              </svg>
            </span>
            <span className="h-middle-stroke letter-stroke">
              <svg className="letter-stroke__svg" viewBox="0 0 118 106">
                <path ref={el => this.activePaths(el)} className="letter-stroke__svg-path" d="M0.1,58c0,0,33.5-0.1,66.8,0.5s63.2,0.5,63.2,0.5" />
              </svg>
            </span>
            <span className="h-right-stroke letter-stroke">
              <svg className="letter-stroke__svg" viewBox="0 0 109 304">
                <path ref={el => this.activePaths(el)} className="letter-stroke__svg-path" d="M59,0c0,0,0.1,114-0.5,195.8S58,314.5,58,314.5" />
              </svg>
            </span>
          </span>
        </span>

        <span className="letter-holder letter-e">
          <span className="letter">E</span>
          <span className="letter-strokes letter-strokes--e">
            <span className="e-left-stroke letter-stroke">
              <svg className="letter-stroke__svg" viewBox="0 0 106 316">
                <path ref={el => this.activePaths(el)} className="letter-stroke__svg-path" d="M59,0c0,0,0.1,114-0.5,195.8S58,314.5,58,314.5" />
              </svg>
            </span>
            <span className="e-top-stroke letter-stroke">
              <svg className="letter-stroke__svg" viewBox="0 0 134 105">
                <path ref={el => this.activePaths(el)} className="letter-stroke__svg-path" d="M0.2,51c0,0,24.5-0.1,57.8,0.5s75.2,0.5,75.2,0.5" />
              </svg>
            </span>
            <span className="e-middle-stroke letter-stroke">
              <svg className="letter-stroke__svg" viewBox="0 0 127 103">
                <path ref={el => this.activePaths(el)} className="letter-stroke__svg-path" d="M0.2,51c0,0,24.5-0.1,57.8,0.5s75.2,0.5,75.2,0.5" />
              </svg>
            </span>
            <span className="e-bottom-stroke letter-stroke">
              <svg className="letter-stroke__svg" viewBox="0 0 136 106">
                <path ref={el => this.activePaths(el)} className="letter-stroke__svg-path" d="M0.2,51c0,0,24.5-0.1,57.8,0.5s75.2,0.5,75.2,0.5" />
              </svg>
            </span>
          </span>
        </span>

        <span className="letter-holder letter-y">
          <span className="letter">Y</span>
          <span className="letter-strokes letter-strokes--y">
            <span className="y-left-stroke letter-stroke">
              <svg className="letter-stroke__svg" viewBox="0 0 196 232">
                <path ref={el => this.activePaths(el)} className="letter-stroke__svg-path" d="M58,0c0,42,13.8,71.5,37,117c24,47,52,80,52,116" />
              </svg>
            </span>
            <span className="y-right-stroke letter-stroke">
              <svg className="letter-stroke__svg" viewBox="0 0 218 215">
                <path ref={el => this.activePaths(el)} className="letter-stroke__svg-path" d="M110.5,0.1c0,0,0.1,83.6-0.5,143.5c-0.5,60-0.5,90-0.5,90" />
              </svg>
            </span>
            <span className="y-bottom-stroke letter-stroke">
              <svg className="letter-stroke__svg" viewBox="0 0 106 122">
                <path ref={el => this.activePaths(el)} className="letter-stroke__svg-path" d="M59,0.1c0,0,0.1,43.5-0.5,76.8S58,125.6,58,125.6" />
              </svg>
            </span>
          </span>
        </span>
      </div>
    );
  }
};

export default withNavItem(NavItemHeyo);

This codepen is an example of someone taking a different route to animate some paths in sequence, though I'm unsure if I can replicate this since my paths are so nested.

Does anyone have any ideas?


Solution

  • Error shows because this in child component represents child component and not HOC. To be able to use such function it needs to be passed by props to the child component. Some pseudo code:

    // HOC
    ...
    render() {
      this.rendered = true;
      return <WrappedComponent {...this.props} activePaths={this.activePaths} />;
    } 
    
    // WRAPPED COMPONENT
    ...
    <path ref={el => this.props.activePaths(el)} />