66

I use reactjs and want to handle scroll with click event.

Firstly, I rendered list of posts with componentDidMount.

Secondly, by click event on each post in list, It will display post detail and scroll to top (because I put post detail to top position of page).

Thirdly, by clicking "close button" in post detail, it will return previous list of posts but I want website will scroll to exactly to position of clicked post.

I use like this:

Click event to view post detail:

inSingle = (post, e) => {
   this.setState({
        post: post,
        theposition: //How to get it here?
   });
   window.scrollTo(0, 0);
}

I want to get state of theposition then I can do scroll exactly to position of clicked post by 'Close event'.

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Hai Tien
  • 2,929
  • 7
  • 36
  • 55
  • What version of react are you using? – dance2die Nov 05 '18 at 16:58
  • @SungKim I am using the latest version – Hai Tien Nov 05 '18 at 16:58
  • Related, vanilla-JS canonical: [Check if a user has scrolled to the bottom (not just the window, but any element)](https://stackoverflow.com/questions/3898130/check-if-a-user-has-scrolled-to-the-bottom-not-just-the-window-but-any-element) – TylerH Mar 03 '23 at 18:49

12 Answers12

80

In case you need to keep on track of the scroll position, you can use react hooks to do so, that way it's possible to check the scroll position any time you need it:

import React, { useState, useEffect } from 'react';

...
// inside component:

const [scrollPosition, setScrollPosition] = useState(0);
const handleScroll = () => {
    const position = window.pageYOffset;
    setScrollPosition(position);
};

useEffect(() => {
    window.addEventListener('scroll', handleScroll, { passive: true });

    return () => {
        window.removeEventListener('scroll', handleScroll);
    };
}, []);

In this case useEffect will behavior similar to componentDidMount, it will fire once the component has rendered, but also in every render, as Jared Beach commented bellow: "window.addEventListener is smart enough to discard subsequent calls with the same parameters". . Make sure to return the cleanup function, similar to what you'd do in componentWillUnmount.

GuCier
  • 6,919
  • 1
  • 29
  • 36
Lucas Andrade
  • 4,315
  • 5
  • 29
  • 50
  • 2
    Appreciate the solution. However, saying `useEffect` is similar to `componentDidMount` is a little misleading. It runs every single render, not just the first. This code works still because window.addEventListener is smart enough to discard subsequent calls with the same parameters – Jared Beach Apr 20 '20 at 15:32
  • 7
    Always giving window.pageYOffset 0 – skyshine Jun 08 '20 at 11:14
  • 7
    He has the second argument to useEffect being an empty array, which is the similar to componentDidMount. If that wasn't there, it would be similar to componentDidUpdate. – efru Sep 28 '21 at 18:29
  • In my solution I added [scrollPosition] as an argument, so it will only change if the state changes. Are there any pros or Cons? I'm relatively new to react. – Juice Mar 04 '22 at 15:15
55

You can use event listener in react like you will use in other js framework or library.

componentDidMount() {
  window.addEventListener('scroll', this.listenToScroll)
}

componentWillUnmount() {
  window.removeEventListener('scroll', this.listenToScroll)
}

listenToScroll = () => {
  const winScroll =
    document.body.scrollTop || document.documentElement.scrollTop

  const height =
    document.documentElement.scrollHeight -
    document.documentElement.clientHeight

  const scrolled = winScroll / height

  this.setState({
    theposition: scrolled,
  })
}
EQuimper
  • 5,811
  • 8
  • 29
  • 42
36

This should work:

this.setState({
    post: post,
    theposition: window.pageYOffset
});
Sabbir Ahmed
  • 1,468
  • 2
  • 16
  • 21
  • 4
    it is bad no ? I mean if you setState, you reRender your component each time, seems to be better to combine useState and useEffect or useLayoutEffect – snoob dogg Nov 22 '21 at 23:15
  • https://github.com/n8tb1t/use-scroll-position use-scroll-position solves this by using throttling technic to avoid too many reflows (the browser to recalculate everything). – Werthis Dec 02 '22 at 12:27
16
import React, { useLayoutEffect, useState } from 'react';

export default function useWindowPosition() {
  const [scrollPosition, setPosition] = useState(0);
  useLayoutEffect(() => {
    function updatePosition() {
      setPosition(window.pageYOffset);
    }
    window.addEventListener('scroll', updatePosition);
    updatePosition();
    return () => window.removeEventListener('scroll', updatePosition);
  }, []);
  return scrollPosition;
}
Bohdan Yashchuk
  • 161
  • 1
  • 2
  • what's the point of using `useLayoutEffect` here ? – Hamid Shoja Oct 18 '22 at 09:47
  • Depends on what will you do with the scroll position, but generally, if you want to perform some DOM measurements (like getting scroll coordinates) it might be better to useLayoutEffect since it runs after DOM mutations have been performed but BEFORE the node gets painted on the screen. Hence if you will further mutate the DOM using that data, you will avoid stuff like screen flickering – Sergei Mar 16 '23 at 14:38
9

Combining some of the other answers, this is exactly what is needed here. Simply use this hook to get the scrollX (horizontal) and scrollY (vertical) positions from the window. It'll be updated as the user scrolls.

/// in useWindowScrollPositions.js

import { useEffect, useState } from 'react'

export const useWindowScrollPositions = () => {

   const [scrollPosition, setPosition] = useState({ scrollX: 0, scrollY: 0 })

   useEffect(() => {
    function updatePosition() {
        setPosition({ scrollX: window.scrollX, scrollY: window.scrollY })
    }

    window.addEventListener('scroll', updatePosition)
    updatePosition()

    return () => window.removeEventListener('scroll', updatePosition)
   }, [])

   return scrollPosition
}

Then call the hook in your function component:

/// in MyComponent.jsx

import { useWindowScrollPositions } from 'path/to/useWindowScrollPositions'

export const MyComponent = () => {
   const { scrollX, scrollY } = useWindowScrollPositions()
   
   return <div>Scroll position is ({scrollX}, {scrollY})</div>
} 

Note that window.pageXOffset and window.pageYOffset, which were used in the other answers from years ago, have been deprecated in favor of window.scrollX and window.scrollY

lwdthe1
  • 1,001
  • 1
  • 16
  • 16
8

You can use the native react event listener.

<div onScroll={(e) => console.log("scrolling!", e.target.scrollTop)}>
    <h3>Some huge div</h3>
</div>
Mint
  • 1,013
  • 1
  • 12
  • 29
4

Like this:

theposition: e.y // or, e.pageY

Or,

theposition: e.clientY - e.currentTarget.getBoundingClientRect().top
Bhojendra Rauniyar
  • 83,432
  • 35
  • 168
  • 231
3

to get current scroll position you can use

horizontal scrolling amount window.pageXOffset

vertical scrolling amount window.pageYOffset

1

this repo helped me a lot https://github.com/n8tb1t/use-scroll-position

yarn add @n8tb1t/use-scroll-position

import { useScrollPosition } from '@n8tb1t/use-scroll-position'


useScrollPosition(({ prevPos, currPos }) => {
 console.log(currPos.x)
 console.log(currPos.y)
})
aitbella
  • 958
  • 9
  • 19
1

Found this on MDN:

element.scrollHeight - Math.abs(element.scrollTop) === element.clientHeight
vancy-pants
  • 1,070
  • 12
  • 13
0

Use window.scrollY instead of window.pageYOffset(deprecated).

  const [scrollPosition, setScrollPosition] = useState(0);
  
  useEffect(() => {
    if (typeof window !== "undefined") {
      
      const handleScroll = () => {
        const position = window.scrollY;
        setScrollPosition(position);
      }

      window.addEventListener("scroll", handleScroll);
   
      return () => window.removeEventListener("scroll", handleScroll);
    }
  }, [])
crystal
  • 185
  • 1
  • 4
0

Also you can use a npm package to track the user scroll position https://www.npmjs.com/package/react-hook-collections

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Apr 30 '23 at 17:14
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/34301413) – Wongjn May 05 '23 at 07:46