3

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.

1
  • What if you use useLayoutEffect instead of useEffect? It should wait for everything to be rendered before running. Commented Aug 9, 2020 at 14:07

4 Answers 4

5

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.

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

1 Comment

Could also do node.scrollIntoView({behavior: 'smooth', block: 'start'});
1

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.

Comments

0
      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} />

</>

    )

Comments

0

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

Comments

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.