6

I have a small starter React JS project I'm working on where a user types text into a field, the text appears above the field in a fixed height box, and the content in that box scrolls down so that the last element added is always visible. (Essentially like a chat box.)

I've attempted to do this using .scrollIntoView(), but for some reason it doesn't scroll all the way to the bottom of the box. Instead it always stays a few pixels from the bottom. (Firefox 88 on macOS Big Sur)

I have a very stripped down demo of what's happening here: https://ui7bx.csb.app/

You can view the source code below and edit it here: https://codesandbox.io/s/tender-dawn-ui7bx?file=/src/index.js:0-1383

I'm baffled as to what could be going wrong here. Any help would be appreciated.

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

class Blog extends React.Component {
  constructor(props) {
    super(props);
    this.sendMsg = this.sendMsg.bind(this);
    this.textInput = React.createRef();
    this.state = {
      posts: [
        {content: "This is decent sized post"},
        {content:"Another post that is a little longer and extends the boundaries of space and time"}
      ]
    };
  }
  sendMsg(event) {
    event.preventDefault(); // stop page from refreshing
    // update state with new post
    let posts = this.state.posts;
    const content = this.textInput.current.value; // get field value
    posts.push({content: content});
    this.setState({ posts: posts });

    //scroll down to the bottom
    this.msgEnd.scrollIntoView({behavior: "smooth"});
  }
  render() {
    const msgList = this.state.posts.map((post, index) => {
      return (
        <div className={"postWrapper"} key={index}>{post.content}</div>
      );
    });
    return (
      <div>
        <div id="list">
          {msgList}
          <div ref={el => {this.msgEnd = el; }}></div>
        </div>
        <form id="chatForm">
          <input type="text" ref={this.textInput} />
          <button onClick={this.sendMsg}>Send</button>
        </form>
      </div>
    );
  }
}

ReactDOM.render(<Blog />, document.getElementById("root"));
3
  • Interesting, other than the first message that "overflows" it appears to consistently scroll the bottom of the messages into view. Chrome 83 MacOS Mojave. Commented Jul 8, 2020 at 1:04
  • @DrewReese Weird - the issue ended up being that I was scrolling before the element had fully rendered. Maybe Chrome 83 is just faster than Firefox at rendering? :) Commented Jul 8, 2020 at 13:50
  • Yup, once @Anthony answered it was overtly obvious you had the order mixed up. Commented Jul 8, 2020 at 15:20

2 Answers 2

7

I think you need the scroll to happen after the posts have been updated, so they are all (including the new one that is added in the sendMsg() method) rendered and the msgEnd ref is up to date.

  componentDidUpdate(prevProps, prevState) {
    //maybe add conditional logic to only scroll when the posts have changed
    this.msgEnd.scrollIntoView({
      block: "nearest",
      inline: "center",
      behavior: "smooth",
      alignToTop: false
    });
  }
Sign up to request clarification or add additional context in comments.

4 Comments

Ah! That’s a good thought. How would you recommend that I check that the new “post” has fully rendered?
it will have if componentDidUpdate is invoked, since your state was updated when sendMsg() was called
Ah that makes sense. I'm rather new to ReactJS so I didn't realize that componentDidUpdate was a React method (and not some custom method you had written.) This did the trick! I imagine I can use prevState and the current state to check if I should call the scrollIntoView code.
yep you can use prevState to verify whether you want to scroll again
1

Maybe the scrollIntoView not work in some cases. Mention in the docs: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView#notes

The element may not be scrolled completely to the top or bottom depending on the layout of other elements.

You can use scrollTop attribute to make it works. Sample code that always works for me:

const targetElement = mainEl.querySelector('#' + itemElement)
    if (targetElement) {
      mainEl.scrollTop = targetElement.offsetTop
    }

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.