3

I have some code inside a component that detects if that component is visible on scroll. That code looks like:

    constructor(props) {
      super(props);
      this.handleScrollAnimation = this.handleScrollAnimation.bind(this);
  }

  componentDidMount() {
    this.handleLoadAnimation();
    window.addEventListener('scroll', _.throttle(this.handleScrollAnimation.bind(this), 300));
  }

  componentWillUnmount() {
    window.removeEventListener('scroll', this.handleScrollAnimation.bind(this));
  }

  handleLoadAnimation() {
    const component = this.CalloutBoxes;
    if (this.isElementInViewport(component)) {
      component.classList.add('already-visible');
    }
  }

  handleScrollAnimation() {
    const component = this.CalloutBoxes;
    if (this.isElementInViewport(component)) {
      component.classList.add('animated');
    }
  }

  isElementInViewport(el) {
    const rect = el.getBoundingClientRect();

    return rect.bottom > 0 &&
      rect.right > 0 &&
      rect.left < (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */ &&
      rect.top < (window.innerHeight || document.documentElement.clientHeight); /* or $(window).height() */
  }

When I navigate to another page I get the error Cannot read property 'getBoundingClientRect' of null. I'm not sure what I need to do to stop this and can't find anything that can give me an idea of what I need to do.

This is my render function of the component:

    render() {
    const styles = {
      image: {
        backgroundImage: `url(${this.props.data.image})`
      }
    };

    return (
      <div
        ref={c => {
          this.CalloutBoxes = c;
        }}
        className="mini-nav-box"
        >
        <Link to={this.props.data.link}>
          <div style={styles.image} className="mini-nav-box-bg"/>
          <div className="mini-nav-box-content">
            <h3>{this.props.data.title}</h3>
            <p>{this.props.data.copy}</p>
          </div>
        </Link>
      </div>
    );
  }

This is where I call the component on page:

{ calloutBoxes.map((box, index) => {
  return <CalloutBoxes key={index} data={box}/>;
})}

EDIT:

I see I have to remove the .bind(this) from the remove and add event listner as they are creating a new function every time. so now my remove event listener looks like this now:

window.removeEventListener('scroll', this.scrollFn);

However I still have the issue of the isElementInViewport function firing on another page which doesn't have one of these components on it.

saunders
  • 969
  • 4
  • 14
  • 25

1 Answers1

1

So I realised I was being very very stupid.

What you need to do is add the debounce to the constructor and remove it from the add event listener.

The constructor code now looks like this:

constructor(props) {
  super(props);
  this.handleScroll = _.debounce(this.handleScrollAnimation.bind(this), 300);
}

Then the componentDidMount looks like this now:

componentDidMount() {
  this.handleLoadAnimation();
  window.addEventListener('scroll', this.handleScroll);
}
saunders
  • 969
  • 4
  • 14
  • 25