Rahul Singh
Rahul Singh

Reputation: 19622

React custom Hook using useRef returns null for the first time the calling component Loads?

I have created a custom hook to scroll the element back into view when the component is scrolled.

export const useComponentIntoView = () => {
  const ref = useRef();
  const {current} = ref;
  if (current) {
    window.scrollTo(0, current.offsetTop );
  }
  return ref;
}

Now i am making use of this in a functional component like

<div ref={useComponentIntoView()}>

So for the first time the current always comes null, i understand that the component is still not mounted so the value is null . but what can we do to get this values always in my custom hook as only for the first navigation the component scroll doesn't work . Is there any work around to this problem .

Upvotes: 4

Views: 17806

Answers (2)

Alvaro
Alvaro

Reputation: 9662

We need to read the ref from useEffect, when it has already been assigned. To call it only on mount, we pass an empty array of dependencies:

const MyComponent = props => {
    const ref = useRef(null);

    useEffect(() => {
        if (ref.current) {
            window.scrollTo(0, ref.current.offsetTop);
        }
    }, []);

    return <div ref={ref} />;
};

In order to have this functionality out of the component, in its own Hook, we can do it this way:

const useComponentIntoView = () => {
    const ref = useRef(null);

    useEffect(() => {
        if (ref.current) {
            window.scrollTo(0, ref.current.offsetTop);
        }
    }, []);

    return ref;
};

const MyComponent = props => {
    const ref = useComponentIntoView();

    return <div ref={ref} />;
};

We could also run the useEffect hook after a certain change. In this case we would need to pass to its array of dependencies, a variable that belongs to a state. This variable can belong to the same Component or an ancestor one. For example:

const MyComponent = props => {
    const [counter, setCounter] = useState(0);
    const ref = useRef(null);

    useEffect(() => {
        if (ref.current) {
            window.scrollTo(0, ref.current.offsetTop);
        }
    }, [counter]);

    return (
        <div ref={ref}>
            <button onClick={() => setCounter(counter => counter + 1)}>
                Click me
            </button>
        </div>
    );
};

In the above example each time the button is clicked it updates the counter state. This update triggers a new render and, as the counter value changed since the last time useEffect was called, it runs the useEffect callback.

Upvotes: 9

smashed-potatoes
smashed-potatoes

Reputation: 2222

As you mention, ref.current is null until after the component is mounted. This is where you can use useEffect - which will fire after the component is mounted, i.e.:

const useComponentIntoView = () => {
  const ref = useRef();
  useEffect(() => {
    if (ref.current) {
      window.scrollTo(0, ref.current.offsetTop );
    }
  });

  return ref;
}

Upvotes: 2

Related Questions