Search code examples
reactjsscroll

useEffect to scroll to bottom not working


So this is my code, testing out a chatbot. useEffect is not working when I refresh, the automatic scroll doesn't work when new message is being received or sent.. what am I missing?

import './App.css';
import './normal.css';
import { useState, useRef, useEffect } from 'react';

function App() {

  const messagesEndRef = useRef(null);
  const [input, setInput] = useState("");
  const [chatLog, setChatLog] = useState([{
    user: "gpt",
    message: "Hello World"
  }])

  function clearChat(){
    setChatLog([]);
  }

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

  async function handleSubmit(e){
    e.preventDefault();
    let chatLogNew = [...chatLog, { user: "me", message: `${input}` } ]
    await setInput("");
    setChatLog(chatLogNew)

    const messages = chatLogNew.map((message) => message.message).join("\n")

    const response = await fetch("http://localhost:3080/", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        message: messages
      })
    });
    const data = await response.json();
    setChatLog([...chatLogNew, { user: "gpt", message: `${data.message}`}])

  }

  return (
    <div className="App">
      <aside className ="sidemenu">
        <div className="title">
            <h1>Title</h1>
        </div>
        <div className="side-menu-button" onClick={clearChat}>
          <span>+</span>
          New chat
        </div>
      </aside>
      <section className="chatbox">
        <div className="chat-log">
          {chatLog.map((message, index) => (
            <ChatMessage key={index} message={message} />
          ))}
        </div>
        <div ref={messagesEndRef} />
        <div className="chat-input-holder">
            <form onSubmit={handleSubmit}>
              <input 
              rows="1"
              value={input}
              onChange={(e)=> setInput(e.target.value)}
              className="chat-input-textarea">
              </input>
            </form>
        </div>
      </section>
    </div>
  );
}

const ChatMessage = ({ message }) => {
  return (
    <div className={`chat-message ${message.user === "gpt" && "chatgpt"}`}>
            <div className="chat-message-center">
            <div className={`avatar ${message.user === "gpt" && "chatgpt"}`}>

              {message.user === "gpt" && "AI"}

              {message.user === "me" && "Me"}
              
              </div>
              <div className="message">
                {message.message}
              </div>
            </div>
          </div>
  )
}

export default App;

Defined the messagesEndRef and inserted the useEffect and put in the dummy div to the last rendered message.

Any ideas? Am I formatting it wrong?

EDIT:

It's working now but I have to have chat-log set to "overflow:scroll" otherwise it doesn't kick in.. for example "overflow:auto" doesn't work.

When it DOES work, it also doesn't scroll to the very end of the box but slightly above.

Any solution to this?


Solution

  • Solved it by adding this code:

    useEffect(() => {
        const chatLogElement = messagesEndRef.current;
        const currentScrollTop = chatLogElement.scrollTop;
        const targetScrollTop = chatLogElement.scrollHeight - chatLogElement.clientHeight;
        const scrollDiff = targetScrollTop - currentScrollTop;
        let startTime;
      
        function scroll(timestamp) {
          if (!startTime) {
            startTime = timestamp;
          }
      
          const elapsedTime = timestamp - startTime;
          const progress = elapsedTime / 200;
      
          chatLogElement.scrollTop = currentScrollTop + (scrollDiff * progress);
      
          if (progress < 1) {
            window.requestAnimationFrame(scroll);
          }
        }
      
        window.requestAnimationFrame(scroll);
      }, [chatLog.length]);
    

    Now it works, even when overflow is set to "auto"