Toggle Class based on scroll React JS

It's Better

import React from 'react';
import { render } from 'react-dom';

class App extends React.Component {
    constructor(props) {
    super(props);

    this.state = {
      isTop: true
    };
    this.onScroll = this.onScroll.bind(this);
  }

  componentDidMount() {
    document.addEventListener('scroll', () => {
      const isTop = window.scrollY < 100;
      if (isTop !== this.state.isTop) {
        this.onScroll(isTop);
      }
    });
  }

  onScroll(isTop) {
    this.setState({ isTop });
  }

  render() {
    return (
      <div style={{ height: '200vh' }}>
        <h2 style={{ position: 'fixed', top: 0 }}>Scroll {this.state.isTop ? 'down' : 'up'}!</h2>
      </div>
    );
  }
} 

render(<App />, document.getElementById('root'));

One way to add a scroll listener is to use the componentDidMount() lifecycle method. Following example should give you an idea:

import React from 'react';
import { render } from 'react-dom';

class App extends React.Component {
  state = {
    isTop: true,
  };

  componentDidMount() {
    document.addEventListener('scroll', () => {
      const isTop = window.scrollY < 100;
      if (isTop !== this.state.isTop) {
          this.setState({ isTop })
      }
    });
  }
  render() {
    return (
      <div style={{ height: '200vh' }}>
        <h2 style={{ position: 'fixed', top: 0 }}>Scroll {this.state.isTop ? 'down' : 'up'}!</h2>
      </div>
    );
  }
} 

render(<App />, document.getElementById('root'));

This changes the Text from "Scroll down" to "Scroll up" when your scrollY position is at 100 and above.

Edit: Should avoid the overkill of updating the state on each scroll. Only update it when the boolean value changes.


 const [scroll, setScroll] = useState(false);

 useEffect(() => {
   window.addEventListener("scroll", () => {
     setScroll(window.scrollY > specify_height_you_want_to_change_after_here);
   });
 }, []); 

Then you can change your class or anything according to scroll.

<nav className={scroll ? "bg-black" : "bg-white"}>...</nav>


For those of you who are reading this question after 2020, I've taken @glennreyes answer and rewritten it using React Hooks:

  const [scroll, setScroll] = useState(0)

  useEffect(() => {
    document.addEventListener("scroll", () => {
      const scrollCheck = window.scrollY < 100
      if (scrollCheck !== scroll) {
        setScroll(scrollCheck)
      }
    })
  })

Bear in mind that, useState has an array of two elements, firstly the state object and secondly the function that updates it.

Along the lines, useEffect helps us replace componentDidmount, the function written currently does not do any clean ups for brevity purposes.

If you find it essential to clean up, you can just return a function inside the useEffect.

You can read comprehensively here.

UPDATE:

If you guys felt like making it modular and even do the clean up, you can do something like this:

  1. Create a custom hook as below;

    import { useState, useEffect } from "react"
    
    export const useScrollHandler = () => {
    // setting initial value to true
    const [scroll, setScroll] = useState(1)
    
    // running on mount
    useEffect(() => {
      const onScroll = () => {
        const scrollCheck = window.scrollY < 10
        if (scrollCheck !== scroll) {
          setScroll(scrollCheck)
        }
      }
    
    // setting the event handler from web API
    document.addEventListener("scroll", onScroll)
    
    // cleaning up from the web API
     return () => {
       document.removeEventListener("scroll", onScroll)
      }
    }, [scroll, setScroll])
    
    return scroll
    
    }
    
  2. Call it inside any component that you find suitable:

    const component = () => {
    
    // calling our custom hook
    const scroll = useScrollHandler()
    
    ....... rest of your code
    
    }