Scroll to bottom of an overflowing div in React
I have a ul
with a max-height
and overflow-y: auto
.
When the user enters enough li
elements, the ul
starts to scroll, but I want the last li
with the form
in it to always be present and viewable to the user.
I've tried implementing a scrollToBottom
function that looks like this:
scrollToBottom() {
this.formLi.scrollIntoView({ behavior: 'smooth' });
}
But that just makes the ul
jump to the top of the screen and show the li
with the form in it as the only visible thing.
Is there a better way to accomplish this? Answers I find tend to be a bit older and use ReactDOM. Thanks!
CSS:
.prompt-box .options-holder {
list-style: none;
padding: 0;
width: 95%;
margin: 12px auto;
max-height: 600px;
overflow-y: auto;
}
HTML:
<ul className='options-holder'>
{
this.state.items.map((item, index) => (
<li key={item.id} className={`option ${index === 0 ? 'first' : ''}`}>
<div className='circle' onClick={this.removeItem} />
<p className='item-text'>{ item.text }</p>
</li>
))
}
<li key={0} className={`option form ${this.state.items.length === 0 ? 'only' : ''}`} ref={el => (this.formLi = el)}>
<div className='circle form' />
<form className='new-item-form' onSubmit={this.handleSubmit}>
<input
autoFocus
className='new-item-input'
placeholder='Type something and press return...'
onChange={this.handleChange}
value={this.state.text}
ref={(input) => (this.formInput = input)} />
</form>
</li>
</ul>
Solution 1:
I have a container that fills the height of the page. This container has display flex and flex direction column. Then I have a Messages
component which is a ul
with one li
per message plus a special AlwaysScrollToBottom
component as the last child that, with the help of useEffect
, scrolls to the bottom of the list.
const AlwaysScrollToBottom = () => {
const elementRef = useRef();
useEffect(() => elementRef.current.scrollIntoView());
return <div ref={elementRef} />;
};
const Messages = ({ items }) => (
<ul>
{items.map(({id, text}) => <li key={id}>{text}</li>)}
<AlwaysScrollToBottom />
</ul>
)
const App = () => (
<div className="container">
<Messages items={[...]}>
<MessageComposer />
</div>
)
The CSS is
.container {
display: flex;
height: 100vh;
flex-direction: column;
}
ul {
flex-grow: 1;
overflow-y: auto;
}
MessageComposer
has been omitted for brevity, it contains the form, the input field, etc. to send messages.
Solution 2:
Use react-scroll library. However you will have to explicitly set ID of your ul
.
import { animateScroll } from "react-scroll";
scrollToBottom() {
animateScroll.scrollToBottom({
containerId: "options-holder"
});
}
You can call scrollToBottom
on setState callback.
For example
this.setState({ items: newItems}, this.scrollToBottom);
Or by using setTimeout.
Or you can even set Element
from react-scroll
and scroll to a certain li
.
Solution 3:
I had a similar issue when it came to rendering an array of components that came from a user import. I ended up using the componentDidUpdate() function to get mine to work.
componentDidUpdate() {
// I was not using an li but may work to keep your div scrolled to the bottom as li's are getting pushed to the div
const objDiv = document.getElementById('div');
objDiv.scrollTop = objDiv.scrollHeight;
}
Solution 4:
Seems like you're building a chat-like interface. You can try wrapping the <ul>
and the div.circle-form
in a separate div like below:
ul{
border:1px solid red;
height:13em;
overflow-y:auto;
position:relative;
}
ul li{
border-bottom:1px solid #ddd;
list-style:none;
margin-left:0;
padding:6px;
}
.wrapper{
background:#eee;
height:15em;
position:relative;
}
.circle-form{
background:#ddd;
height:2em;
padding:3px;
position:absolute;
bottom:0;
left:0;
right:0;
z-index:2;
}
.circle-form input[type=text]{
padding:8px;
width:50%;
}
<div class="wrapper">
<ul id="list">
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
</ul>
<div class="circle-form">
<input type="text" name="type_here" placeholder="Enter something..." autofocus/>
</div>
</div>
EDIT
And to scroll to the bottom of the list with javascript
var list = document.getElementById("list");
list.scrollTop = list.offsetHeight;