SI

[ Using the useRef Hook ]

March 03, 2019

🕰 4 min read

At Foodnome, I wanted to create a fixed badge that appears if the user scrolls to the top of the page and disappears as the user scrolls to the bottom of the page.

scroll feature demonstration.

scroll feature demonstration.

To accomplish this, I knew exactly what to do (or that is what I thought):

  • use a useState hook to keep the old scroll position in local state.
  • add a useEffect to add an event listener to the window to listen to changes in the scroll position, and fire a callback function to update the internal scroll position state. Return a callback to remove the event listener when the component unmount.
  • return the difference between the new scroll position (window.pageYOffset) and the old position (oldScrollPos). If the difference is positive, then the user is scrolling to the bottom of the page. Otherwise, the user is scrolling back to the top.
function useScrollDirection() {
  /*
  	return value:
  		true => user is scrolling to the bottom of the page.
    	false => user is scrolling to the top of the page.
  */
  const [oldScrollPos, setOldScrollPos] = React.useState(0)

  React.useEffect(() => {
    function onScroll() {
      setOldScrollPos(window.pageYOffset)
    }
    window.addEventListener('scroll', onScroll)
    return () => window.removeEventListener('scroll', onScroll)
  }, [])

  // current scroll position minus the old scroll position saved in state.
  const difference = window.pageYOffset - oldScrollPos

  return difference > 0
}

Then in my component using this custom hook:

function EventDetail(props) {
  // use the hook to listen to changes in the scroll position
  const scrollUp = useScrollDirection()
  return (
    <div>
      {/* do something here to react to changes in the scroll direction */}
    </div>
  )
}

This didn’t work 😞. When I added a console.log of the difference into my custom hook, I quickly realized that the old scroll state saved in oldScrollPos was exactly the same as the new scroll state window.pageYOffset (and hence, difference was equal to 0.) So, I needed to figure out how to hold onto this old scroll state and I couldn’t figure it out. At some point, I remembered a blog post by Dan Abramov about keeping some mutable state around using refs. I’ve read about using refs for referencing and focusing input fields and I used to use refs when dealing with setInterval in a class based component. For example,

class MyComponent extends React.Component {
  state = { count: 0 }

  componentDidMount() {
    this.interval = setInterval(
      () =>
        this.setState(({ count }) => ({
          count: count + 1,
        })),
      1000
    )
  }

  componentWillUnmount() {
    clearInterval(this.interval)
  }

  render() {
    return <p>{this.state.count}</p>
  }
}

Anyway, this is exactly what I needed to complete my custom hook! 🎣

function useScrollDirection() {
  /*
  	return value:
  		true => user is scrolling to the bottom of the page.
    	false => user is scrolling to the top of the page.
  */

  // save the new scroll position in state
  const [scrollPos, setScrollPos] = React.useState(0)
  // useRef Hook to save the old scroll state.
  const oldScrollPos = React.useRef(0)

  React.useEffect(() => {
    function onScroll() {
      setScrollPos(window.pageYOffset)
      // save the old scroll position in the ref
      oldScrollPos.current = window.pageYOffset
    }
    window.addEventListener('scroll', onScroll)
    return () => window.removeEventListener('scroll', onScroll)
  }, [])

  // current scroll position minus the old scroll position saved in state.
  const difference = scrollPos - oldScrollPos.current

  return difference > 0
}

I am still not 100% sure if this is the best way of doing this but, I am proud I found a solution to the feature I was trying to implement using the useRef hook!

Special thanks to @donavon for the valuable feedback!


a not so professional head shot.

Scott Iwako

👋 Hello, thanks for the read! If you found my work helpful, have constructive feedback, or just want to say hello, connect with me on social media. Thanks in advance!