Home > OS >  why useRef current value , isn't sharing trough custom hook?
why useRef current value , isn't sharing trough custom hook?

Time:11-02

I wanted to calculate the user scroll height , so I created a custom hook. and I wanted to share this value to another component. but it doesnt work. code:

const useScroll = () => {
  let scrollHeight = useRef(0);

  const scroll = () => {
    scrollHeight.current =
      window.pageYOffset ||
      (document.documentElement || document.body.parentNode || document.body)
        .scrollTop;
  };

  useEffect(() => {
    window.addEventListener("scroll", scroll);

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

  return scrollHeight.current;
};

export default useScroll;

the value is not updating here.

but if I use useState here , it works. but that causes tremendous amount of component re-rendering. can you have any idea , how its happening?

CodePudding user response:

import { useRef } from 'react';
import throttle from 'lodash.throttle';

/**
 * Hook to return the throttled function
 * @param fn function to throttl
 * @param delay throttl delay
 */
const useThrottle = (fn, delay = 500) => {
  // https://stackoverflow.com/a/64856090/11667949
  const throttledFn = useRef(throttle(fn, delay)).current;

  return throttledFn;
};

export default useThrottle;

then, in your custom hook:

const scroll = () => {
    scrollHeight.current =
      window.pageYOffset ||
      (document.documentElement || document.body.parentNode || document.body)
        .scrollTop;
  };

const throttledScroll = useThrottle(scroll)

Also, I like to point out that you are not clearing your effect. You should be:

useEffect(() => {
    window.addEventListener("scroll", throttledScroll);

    return () => {
      window.removeEventListener("scroll", throttledScroll); // remove Listener
    };
  }, [throttledScroll]); // this will never change, but it is good to add it here. (We've also cleaned up effect)

CodePudding user response:

Since the hook won't rerender you will only get the return value once. What you can do, is to create a useRef-const in the useScroll hook. The useScroll hook returns the reference of the useRef-const when the hook gets mounted. Because it's a reference you can write the changes in the useScroll hook to the useRef-const and read it's newest value in a component which implemented the hook. To reduce multiple event listeners you should implement the hook once in the parent component and pass the useRef-const reference to the child components. I made an example for you.

The hook:

import { useCallback, useEffect, useRef } from "react";

export const useScroll = () => {
    const userScrollHeight = useRef(0);

    const scroll = useCallback(() => {
        userScrollHeight.current =
            window.pageYOffset ||
            (document.documentElement || document.body.parentNode || document.body)
                .scrollTop;
    }, []);

    useEffect(() => {
        window.addEventListener("scroll", scroll);

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

    return userScrollHeight;
};

The parent component:

import { SomeChild, SomeOtherChild } from "./SomeChildren";
import { useScroll } from "./ScrollHook";

const App = () => {
  const userScrollHeight = useScroll();

  return (
    <div>
      <SomeChild userScrollHeight={userScrollHeight} />
      <SomeOtherChild userScrollHeight={userScrollHeight} />
    </div>
  );
};

export default App;

The child components:

export const SomeChild = ({ userScrollHeight }) => {
    const someButtonClickHandlerWhichPrintsUserScrollHeight = () => {
        console.log("userScrollHeight from SomeChild", userScrollHeight.current);
    };

    return (
        <div style={{
            width: "100vw",
            height: "100vh",
            backgroundColor: "aqua"
        }}>
            <h1>SomeChild 1</h1>
            <button onClick={() => someButtonClickHandlerWhichPrintsUserScrollHeight()}>Console.log userScrollHeight</button>
        </div>
    );
};

export const SomeOtherChild = ({ userScrollHeight }) => {
    const someButtonClickHandlerWhichPrintsUserScrollHeight = () => {
        console.log("userScrollHeight from SomeOtherChild", userScrollHeight.current);
    };

    return (
        <div style={{
            width: "100vw",
            height: "100vh",
            backgroundColor: "orange"
        }}>
            <h1>SomeOtherChild 1</h1>
            <button onClick={() => someButtonClickHandlerWhichPrintsUserScrollHeight()}>Console.log userScrollHeight</button>
        </div>
    );
};
  • Related