4

I have a React app with basically a fixed header -- <AppHeader> -- and the rest of the content -- <AppMain>. What I'm trying to do is add a drop shadow to the <AppHeader> when the window is scrolled. So I'm attaching an onScroll handler to a DIV around <AppMain>, and when I detect it's scrolled, I set a state variable scrolled, which gets passed to <AppHeader>, and that component can then deal with adding the appropriate drop shadow class. So my code more or less looks like:

class App extends Component {
  _handleScroll(e) {
    console.log('scrolling:', e.target.scrollTop);
    if (e.target.scrollTop > 0) {
      this.setState({ scrolled: true });
    } else {
      this.setState({ scrolled: false });
    }
  }

  constructor(props) {
    super(props);

    this._handleScroll = this._handleScroll.bind(this);
    this.state = {
      scrolled: false
    };
  }

  render() {
    return (
      <div className="App">
        <AppHeader scrolled={this.state.scrolled} />
        <div className="AppMainScroll" onScroll={this._handleScroll}>
          <AppMain />
        </div>
      </div>
    );
  }
}

The above code works well enough, and my <AppHeader> gets the appropriate prop value when the window is scrolled. However, I notice in my console that the _handleScroll() function keeps getting triggered when the div is scrolled, even if I'm not actively scrolling / interacting with the window. As I'm typing this, my console.log() in _handleScroll() has fired 2000 times, and that window isn't even in the foreground. If I scroll all the way back to the top, _handleScroll() no longer gets triggered.

Am I using onScroll and setState incorrectly here?

accelerate
  • 1,215
  • 3
  • 16
  • 30
  • Have you tried to debounce the scroll handler? – Danny Delott Aug 16 '17 at 19:11
  • I'm not sure why this is, but you could try attaching your handler to `window` directly in `componentDidMount` (remember to remove the handler in `componentWillUnmount`). This way you also don't need a wrapper. – John Weisz Aug 16 '17 at 19:12
  • Is the drop shadow constant? For example, like stack overflows header dropshadow(adds a slight shadow when document top is not 0)? Or does the drop shadow only appear when your literally scrolling. – Dan Zuzevich Aug 16 '17 at 19:45
  • minor comment but you can replace your `if` statement with: `this.setState({ scrolled: e.target.scrollTop > 0 })` – azium Aug 16 '17 at 20:10
  • @DannyDelott I just tried adding lodash's `debounce` as [detailed here](https://stackoverflow.com/questions/23123138/perform-debounce-in-react-js/24679479#24679479), but now the event is firing every 500ms (my debounce timeout), instead of immediately. – accelerate Aug 16 '17 at 21:18
  • @accelerate did the answer below work for you? https://stackoverflow.com/a/45723034/2410624 – byumark Aug 17 '17 at 15:43

1 Answers1

1

I think this is what you're missing.

componentDidMount() {
  window.onscroll = () => this._handleScroll()
}

_handleScroll() {
  console.log('scrolling:', document.documentElement.scrollTop);
  if (document.documentElement.scrollTop > 0) {
    this.setState({ scrolled: true });
  } else {
    this.setState({ scrolled: false });
  }
}

https://codesandbox.io/s/nk6o110464

byumark
  • 298
  • 1
  • 6
  • I just went back to the codesandbox.io link and it didn't work like it did before. After some playing around a bit I found that changing `document.body.scrollTop` to `document.documentElement.scrollTop` got it working again. The original code showed `0` and wouldn't change when I scrolled. I've updated the answer and the codesandbox.io. – byumark Sep 22 '17 at 04:00