by

Streaming chat scroll to bottom with React

Let’s say you’re making a chat application. You are? Great. And we’re streaming text from tokens as they are generated from the server.

But the text scrolls off the page!

Let’s scroll into view

We can fix this by using scrollIntoView on the element we want to view.

const element = document.querySelector("#my-element");
element.scrollIntoView();

By default this scrolls to the top, left are at 0, 0 for the element relative to the viewport.

Get to bottom

To get to the bottom of the element, where the text is going. Wave to the text as it passes us by. We can jump to the bottom with scrollToView as well!

// Setting scrollIntoView to false reverses to the bottom
element.scrollIntoView(false);

alignToTop

  • If true, the top of the element will be aligned to the top of the visible area of the scrollable ancestor. This is the default value.
  • If false, the bottom of the element will be aligned to the bottom of the visible area of the scrollable ancestor.

React example

Next we can use this to update the scroll every time something changes, like streaming chat from a LLM.

Here is an example using React to watch a value and then scroll to the bottom every time it updates. Thanks to Deepankar Bhade for sharing the code.

// Thanks to Deepankar Bhade for the original code and idea
// https://dev.to/deepcodes/automatic-scrolling-for-chat-app-in-1-line-of-code-react-hook-3lm1
export function useChatScroll<T>(
  dep: T,
  options: boolean | ScrollIntoViewOptions,
): MutableRefObject<HTMLDivElement | null> {
  const ref = useRef<HTMLDivElement>();
  useEffect(() => {
    if (ref.current) {
      ref.current.scrollIntoView(options);
    }
  }, [dep]);
  return ref;
}

function Example() {
  const [streamingChat, setStreamingChat] = useState([]);
  // false here scrolls to bottom of element
  const ref = useChatScroll(streamingChat, false);

  useEffect(() => {
    // Setup your streaming.
    setStreamingChat([{ content: "hello" }]);
  }, []);

  return <div ref={ref}>{streamingChat.map(msg => msg.content)}</div>;
}

References