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>
);
};
CodePudding user response:
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, Fragment } 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 (
<Fragment>
<div
ref={listItems}
style={{...}}
>
{numbers.map((number) => (...))}
</div>
<button onClick={() => {...}}>
Add number
</button>
</Fragment>
);
};
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);