useScrollToEnd

Here's an example of a custom hook that uses the useEffect Hook to track whether the user has scrolled to the bottom of a container:

import { useState, useEffect } from 'react'

function useScrollToEnd(ref) {
  const [isAtEnd, setIsAtEnd] = useState(false)

  useEffect(() => {
    const container = ref.current
    function handleScroll() {
      setIsAtEnd(
        container.scrollTop + container.clientHeight === container.scrollHeight
      )
    }

    container.addEventListener('scroll', handleScroll)

    return () => {
      container.removeEventListener('scroll', handleScroll)
    }
  }, [ref])

  return isAtEnd
}

This custom hook takes a ref as a parameter and returns a boolean indicating whether the user has scrolled to the end of the container referenced by the ref. We use the useEffect Hook to attach a scroll event listener to the container, which updates the isAtEnd state variable whenever the user scrolls.

To use this custom hook, you would first create a ref to the container you want to track:

import { useRef } from 'react'

function MyComponent() {
  const containerRef = useRef(null)

  // ...
}

You would then call the useScrollToEnd hook and pass in the ref:

function MyComponent() {
  const containerRef = useRef(null)
  const isAtEnd = useScrollToEnd(containerRef)

  // ...
}

Finally, you could use the isAtEnd variable to conditionally render some UI when the user reaches the end of the container:

const messages = [
  { id: 1, text: 'Hello, world!' },
  { id: 2, text: 'How are you?' },
  { id: 3, text: 'I am fine.' },
  { id: 4, text: 'Goodbye!' },
]

function MyComponent() {
  const containerRef = useRef(null)
  const isAtEnd = useScrollToEnd(containerRef)

  return (
    <div ref={containerRef}>
      {messages.map((message) => (
        <div key={message.id}>{message.text}</div>
      ))}
      {isAtEnd && <div>End of messages!</div>}
    </div>
  )
}

In this example, we render a list of messages and display a message at the bottom of the list when the user has scrolled to the end. The isAtEnd variable is used to conditionally render the "End of messages!" message.

Codepen Example

You may need to use a gap property. The gap is used in the handleScroll function to account for any potential difference between the actual position of the scrollbar and the calculated position based on the container's scroll height and client height. This difference can occur due to rounding errors or other factors, and the gap helps ensure that the isAtEnd state is updated correctly when the scrollbar is near the bottom of the container. By adding a gap, the condition for isAtEnd becomes container.scrollTop >= container.scrollHeight - container.clientHeight - gap, which allows for a small margin of error before considering the scrollbar to be at the very end.