quieri
quieri

Reputation: 344

Scroll to element on page load with React Hooks

I'm trying to create a functional component that fetches data from an API and renders it to a list. After the data is fetched and rendered I want to check if the URL id and list item is equal, if they are then the list item should be scrolled into view.

Below is my code:

import React, { Fragment, useState, useEffect, useRef } from "react";

export default function ListComponent(props) {
  const scrollTarget = useRef();

  const [items, setItems] = useState([]);
  const [scrollTargetItemId, setScrollTargetItemId] = useState("");

  useEffect(() => {
    const fetchData = async () => {
      let response = await fetch("someurl").then((res) => res.json());

      setItems(response);
    };
    
    fetchData();

    if (props.targetId) {
      setScrollTargetItemId(props.targetId)
    }

    if (scrollTarget.current) {
      window.scrollTo(0, scrollTarget.current.offsetTop)
    }
  }, [props]);

  let itemsToRender = [];
  itemsToRender = reports.map((report) => {   

    return (
      <li
        key={report._id}
        ref={item._id === scrollTargetItemId ? scrollTarget : null}
      >
        {item.payload}
      </li>
    );
  });

  return (
    <Fragment>
          <ul>{itemsToRender}</ul>
    </Fragment>
  );
}

My problem here is that scrollTarget.current is always undefined. Please advice what I'm doing wrong. Thanks in advance.

Upvotes: 3

Views: 6821

Answers (4)

Brent Washburne
Brent Washburne

Reputation: 13138

React Hooks will delay the scrolling until the page is ready:

useEffect(() => {
    const element = document.getElementById('id')
    if (element)
        element.scrollIntoView({ behavior: 'smooth' })
}, [])

If the element is dynamic and based on a variable, add them to the Effect hook:

const [variable, setVariable] = useState()
const id = 'id'

useEffect(() => {
    const element = document.getElementById(id)
    if (element)
        element.scrollIntoView({ behavior: 'smooth' })
}, [variable])

Upvotes: 0

quieri
quieri

Reputation: 344

Using useCallback, as @yagiro suggested, did the trick!

My code ended up like this:

const scroll = useCallback(node => {
    if (node !== null) {
      window.scrollTo({
        top: node.getBoundingClientRect().top,
        behavior: "smooth"
      })
    }
  }, []);

And then I just conditionally set the ref={scroll} on the node that you want to scroll to.

Upvotes: 5

Saqib Altaf
Saqib Altaf

Reputation: 81

      constructor(props) {
            thi.modal = React.createRef();
          
        }
    
    handleSwitch() {
            // debugger
            this.setState({ errors: [] }, function () {
                this.modal.current.openModal('signup') // it will call function of child component of Modal
            });
            // debugger
        }
    
    
    return(
    <>
 
    <button className="login-button" onClick={this.handleSwitch}>Log in with email</button>

  
    <Modal ref={this.modal} />

</>

    )

Upvotes: 0

yagiro
yagiro

Reputation: 777

That is because when a reference is changed, it does not cause a re-render.

From React's docs: https://reactjs.org/docs/hooks-reference.html#useref

Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.

Upvotes: 1

Related Questions