Emre
Emre

Reputation: 153

Restore scroll position in React Hooks with useRef is shaky

I want to scroll to my previous window position by setting inside a useEffect the window position back to its previous state. To get the previous state, I am using useRef.

The Component was once class-based and there it worked perfectly. After I refactored it to hooks, this "shaky" behavior started.

Declaring the useRef right at the beginning

const scrollRef = useRef(window.pageYOffset);

Whenever the component re-renders:

scrollRef.current = window.pageYOffset;

When the state gets updated:

useEffect(() => {
  window.scrollTo(0, scrollRef.current)
});

The Complete Code:

export default () => {
   const scrollRef = useRef(window.pageYOffset);
   ...
   scrollRef.current = window.pageYOffset;
   useEffect(() => {
      window.scrollTo(0, scrollRef.current)
   });

   return (
      ...
   );
}

On state update, I want to change back to the previous window position by not having this "shaky" behavior. (By shaky I mean it looks like he scrolls to the top and right after to the previous position so it looks like it shakes)

Upvotes: 5

Views: 10680

Answers (2)

Scott Wager
Scott Wager

Reputation: 868

If I understand correctly, use useLayoutEffect instead of useEffect. This will scroll back to the top before the component is rendered. Remember to add the dependency array.

Upvotes: 1

Fabian Hinsenkamp
Fabian Hinsenkamp

Reputation: 302

The solution to your problem could look as follows:

sandbox

First create a custom usePrevious hook. This is just another function which uses the new useRef method to store the previous scroll value and only updates it when a new value is passed to it.

// Hook
function usePrevious(value) {
  // The ref object is a generic container whose current property is mutable ...
  // ... and can hold any value, similar to an instance property on a class
  const ref = useRef();
  
  // Store current value in ref
  useEffect(() => {
    ref.current = value;
  }, [value]); // Only re-run if value changes
  
  // Return previous value (happens before update in useEffect above)
  return ref.current;
}

In the actual component we declare a previousPosition variable. Every time the component re-renders, the our customHook is executed with the current position. It returns the previous position which then gets assigned.

Also with each render, the useEffect method is being executed, as we do not pass an Array as second argument. There we just compare the current scroll position with the previous and scroll back to the previous in case it changed.

function Scroll(props){
  const prevPosition = usePrevious(window.pageYOffset);
   // Update scoll position with each update
   useEffect(() => {
     if(window.pageYOffset !== prevPosition){
         window.scrollTo(0, prevPosition);
     }  
   });

   return (
      <div>
      ...
      </div>
   );
}

Upvotes: 1

Related Questions