3

I have list of comments. Every time user adds a new comment, I want that div and that div only to scroll to bottom. That div height is fixed and overflow is also set to scroll. I have found some answers to use dummy-div at the end of the comment list to scroll. I encountered 2 problems with that approach.

  1. When used scrollIntoView to that div element, the entire body scrolls. Which is unwanted. I just want that div body to scroll to the last comment.

  2. Say, I have a function, upon calling, scrolls to bottom. handleScrollToBottom. I called it right after the comment was added. But, it doesn't scroll to the bottom. Which makes sense. Because, if I am not wrong, updating the state doesn't update the state instantly. Let me demonstrate more with code

  const Comment = () => {
  const ref = useRef();
  const handleScrollToBottom = () => {
    //
    ref.current.scrollIntoView();
  };

  const addComment = () => {
    //
    setAllComments((pre) => [...pre, { cmt: newCmt }]);
    // Doesn't work here
    handleScrollToBottom();
  };

  return (
    <div>
      {allComments.map((cmt) => (
        <Comment cmt={cmt} />
      ))}
      {/* Dummy Div to scroll to bottom */}
      <div ref={ref}></div>
    </div>
  );
};

That was the second problem. So, I used useEffect and put allComments.length as a dependency.

useEffect(() => {
  // Deleting a comment calls this function
  handleScrollToBottom();
}, [allComments.length]);

That does work. But, the problem is, when a comment was deleted, it scrolls to bottom becasue the comments length changes which is not desired. Does anyone have any solution for my problem? I really appreciate it.

1
  • 1
    Yes, for sure. Just checked them. Will try out with code and accept the correct answer. Thanks to you all for helping. Commented Aug 31, 2022 at 7:50

3 Answers 3

1

If I understand the problem correctly you want the scrolling to happen only when a new comment is added.

So can you create a react state that keeps track of only the added comments, if that changes then scroll, this would ignore deletes.

const [commentAdded, setCommentAdded] = useState(0)

const addComment = () => {

     setAllComments((pre) => [...pre, { cmt: newCmt }]);
     setCommentAdded(++commentAdded)
};


useEffect(() => {
     handleScrollToBottom();
 }, [commentAdded]);
Sign up to request clarification or add additional context in comments.

Comments

1

Instead of using state to check if comments are added or removed, like @wpw has, you can add another ref to keep track of whether comments were added or deleted. And only trigger scrollIntoView when the ref reflects comments are added:

const Comment = () => {
  const ref = useRef();
  const commentsAddedRef = useRef(false); // Checks if comments are added or removed
  const handleScrollToBottom = () => {
    //
    ref.current.scrollIntoView();
  };

  const addComment = () => {
    commentsAddedRef.current = true; // Set ref to true because comment is added
    setAllComments((pre) => [...pre, { cmt: newCmt }]);
  };

  const removeComment = () => {
    commentsAddedRef.current = false; // Set ref to false because comment is removed
    ...
  }

  useEffect(() => {
    if(commentsAddedRef.current){ // Only triggers if comments are added
      handleScrollToBottom();
    }
  }, [allComments]);

  return (
    <div>
      {allComments.map((cmt) => (
        <Comment cmt={cmt} />
      ))}
      {/* Dummy Div to scroll to bottom */}
      <div ref={ref}></div>
    </div>
  );
};

1 Comment

I don't think running the useEffect with the allComments.length is a good idea, useEffect should trigger if a prop or state variable changed, the length of an array is not a good handle to run the effect. But, he could try using a ref in some way, that sounds like a a good idea, using a ref though is a different approach than leaning on react state, not sure how it would impact the rest of the functionality.
0

If you use class component, then you can add a callback to the setState inside the addComment method.
This way there'll be no impact when a comment is deleted.

class CommentList extends Component {

    constructor(props) {
        super(props);
        this.ref = createRef();
        this.state = {allComments: []};
    }

    scrollToBottom = () => {
        this.ref.current?.scrollIntoView();
    }

    addComment = () => {
        this.setState((state) => (
           {allComments: state.allComments.concat({ cmt: newCmt })}
        ), ()=> {
            scrollToBottom();
        });
    }

    render() {
       return <div>
           {
               this.state.allComments.map(c=><Comment cmt={c}>)
           } 
           <div ref={this.ref}></div></div>;
       </div>
    }
}

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.