2

I am trying to create a list in a React app, which the user can append items to. This list is in a div with a fixed height, so after some items, the user has to scroll. The behaviour I want to achieve is that when the user adds a new item to the list, the list scrolls to the end. What always happens, regardless of which solution I try to use, is that the whole page scrolls as well. I want the page to stay exactly where it is - the list itself should not move - and only the list div to scroll to its end.

This is my minimal working example, if you put this in a scrollable page that is.

const Test = () => {
    const [num, setNum] = useState([0, 1, 2]);
    const last = useRef(null);

    useEffect(() => {
        last.current.scrollIntoView({ behavior: "smooth" });
    }, [num]);

    return (
        <div
            style={{
                overflow: "auto",
                width: "100%",
                height: 300,
                background: "green",
                flexDirection: "column",
                justifyContent: "flex-start",
            }}>
            {num.map((numer) => (
                <div key={numer} style={{ height: 20, margin: 2, color: "blue" }}>
                    {numer}
                </div>
            ))}
            <button
                ref={last}
                onClick={() => {
                    setNum([...num, num.slice(-1)[0] + 1]);
                }}>
                Add number
            </button>
        </div>
    );
};

1 Answer 1

4

I'm assuming you want the list to be scrollable, and the button SHOULD NOT move when items in the list increase. Therefore your button element should not be separated from parent div element that is scrollable using overflow: auto.

(Optional) For a better practice, your useRef should be referencing the parent div instead of the button element and you should name your variables properly.

After applying those changes, your code would look something like this:

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

const Test = () => {
  const [numbers, setNumbers] = useState([0, 1, 2]);
  const listItems = useRef(null);

  useEffect(() => {
    const lastItem = listItems.current.lastElementChild;
    if (lastItem) {
      lastItem.scrollIntoView({ behavior: "smooth", block: "nearest" });
    }
  }, [numbers]);

  return (
    <div ref={listItems} style={{...}}>
      {numbers.map((number) => ...)}
      <button onClick={() => {...}}>Add number</button>
    </div>
  );
};

export default Test;

NOTE: The ... means that it's the same as your previous code.

BONUS TIP: If you want to create an array of numbers from 1...n, you can do this instead of manually typing all the numbers.

// Replace `n` with any numbers
const numbers = Array.from(Array(n)).map((_, i) => i + 1);
console.log(numbers);
Sign up to request clarification or add additional context in comments.

5 Comments

You assume wrong, as of right now the design has the button in the list. Your solution also scrolls not only the listItems div, but the whole page as well.
With scrollIntoView({behavior: 'smooth', block: 'nearest'}) it works as intended. Thanks for the help, the concept of accessing elements through their parent element was new to me and I see many uses for it.
If you add the full scroll options ill mark it as the accepted answer.
Do you want to display numbers on initial load of the page?
Do you want to display the button inside or outside the list of numbers?

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.