76

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'.

3

12 Answers 12

88

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.

Sign up to request clarification or add additional context in comments.

4 Comments

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
Always giving window.pageYOffset 0
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.
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.
57

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,
  })
}

Comments

35

This should work:

this.setState({
  post: post,
  theposition: window.pageYOffset
});

EDIT: To update the vertical position every time you scroll, you could utilize event listeners like this (also suggested by Lucas Andrade in a separate answer):

const [scrollYPosition, setScrollYPosition] = React.useState(0);

const handleScroll = () => {
    const newScrollYPosition = window.pageYOffset;
    setScrollYPosition(newScrollYPosition);
};

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

// Now the vertical position is available with `scrollYPosition`
console.log(scrollYPosition)

And here's a bit of trivia:

window.pageYOffset is an alias of window.scrollY property which returns the number of pixels that the document is currently scrolled vertically from the origin, i.e. Y coordinate of the top edge of the current viewport. If the document is not scrolled at all up or down, then scrollY is 0.

Similarly, you can get the number of pixels the document is scrolled horizontally from the origin using scrollX property (window.pageXOffset alias).

Reference: https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY

2 Comments

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
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).
18
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;
}

2 Comments

what's the point of using useLayoutEffect here ?
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
10

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

Comments

8

You can use the native react event listener.

<div onScroll={(e) => console.log("scrolling!", e.target.scrollTop)}>
    <h3>Some huge div</h3>
</div>

Comments

4

Like this:

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

Or,

theposition: e.clientY - e.currentTarget.getBoundingClientRect().top

Comments

3

to get current scroll position you can use

horizontal scrolling amount window.pageXOffset

vertical scrolling amount window.pageYOffset

Comments

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)
})

Comments

1

Found this on MDN:

element.scrollHeight - Math.abs(element.scrollTop) === element.clientHeight

Comments

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);
    }
  }, [])

Comments

0

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

2 Comments

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.
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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.