How to scroll up history in React chat page

3.6k views Asked by At

*Trying to show a chat history with infinite reload similar to Skype or any popular chat app

In a chat page. If my chat messages limit is 10 messages.

And the chat has 30.

It will show latest 10 when I load the chat.

When I scroll to the top I want to see the previous 10.

I tried this myself and the scroll position stays the same but the messages load in the view.

It should load to the top and hold the scroll position.

How can this be done?

Here's my page: https://pastebin.com/36xZPG1W

import React, { useRef, useState, useEffect } from 'react';
import produce from 'immer';
import dayjs from 'dayjs';
import { WithT } from 'i18next';
 
import * as ErrorHandler from 'components/ErrorHandler';
import useOnScreen from 'utils/useOnScreen';
 
import getLang from 'utils/getLang';
 
import Message from './Message';
const limit = 10;
const lang = getLang();
interface IMessagesProps extends WithT {
  messages: any;
  currentUserID: string;
  chatID: string;
  fetchMore: any;
  typingText: any;
  setSelectedMsg: any;
  removeMessage: any;
}
 
const Messages: React.FC<IMessagesProps> = ({
  messages,
  currentUserID,
  chatID,
  fetchMore,
  setSelectedMsg,
  removeMessage,
  t,
}) => {
  const elementRef = useRef(null);
  const isOnScreen = useOnScreen(elementRef);
  const topElementRef = useRef(null);
  const topIsOnScreen = useOnScreen(topElementRef);
 
  const isUserInside = useRef(true);
  const scroller = useRef<HTMLDivElement>(null);
  const messagesEnd = useRef<HTMLDivElement>(null);
  const [hasMore, setHasMore] = useState(true);
 
  useEffect(() => {
    scrollToBottom();
  }, []);
 
  useEffect(() => {
    autoscroll();
  }, [messages]);
 
  //NOT WORKING
  const autoscroll = () => {
    // Visible height
    const visibleHeight = scroller.current.offsetHeight;
 
    // Height of messages container
    const containerHeight = scroller.current.scrollHeight;
 
    // How far have I scrolled?
    const scrollOffset = scroller.current.scrollTop + visibleHeight;
    // New message element
    const firstChild = scroller.current.firstElementChild;
    console.log(`visibleHeight`, visibleHeight);
    console.log(`containerHeight`, containerHeight);
    console.log(`scrollOffset`, scrollOffset);
    console.log(`firstChild`, firstChild.offsetHeight);
    console.log(`firstChild`, firstChild.scrollHeight);
    console.log(`firstChild`, firstChild);
    scroller.current.scrollTop = scrollOffset;
    // // Height of the new message
    // const newMessageStyles = getComputedStyle($newMessage)
    // const newMessageMargin = parseInt(newMessageStyles.marginBottom)
    // const newMessageHeight = $newMessage.offsetHeight + newMessageMargin
 
    // // Visible height
    // const visibleHeight = $messages.offsetHeight
 
    // // Height of messages container
    // const containerHeight = $messages.scrollHeight
 
    // // How far have I scrolled?
    // const scrollOffset = $messages.scrollTop + visibleHeight
 
    // if (containerHeight - newMessageHeight <= scrollOffset) {
    //     $messages.scrollTop = $messages.scrollHeight
    // }
  };
 
  const fetchDataForScrollUp = cursor => {
    ErrorHandler.setBreadcrumb('fetch more messages');
    if (!hasMore) {
      return;
    }
    fetchMore({
      variables: {
        chatID,
        limit,
        cursor,
      },
      updateQuery: (previousResult, { fetchMoreResult }) => {
        if (!fetchMoreResult?.getMessages || fetchMoreResult.getMessages.messages.length < limit) {
          setHasMore(false);
          return previousResult;
        }
        const newData = produce(previousResult, draftState => {
          draftState.getMessages.messages = [...previousResult.getMessages.messages, ...fetchMoreResult.getMessages.messages];
        });
 
        return newData;
      },
    });
  };
 
  if (messages?.length >= limit) {
    if (topIsOnScreen) {
      fetchDataForScrollUp(messages[messages.length - 1].id);
    }
  }
 
  if (isOnScreen) {
    isUserInside.current = true;
  } else {
    isUserInside.current = false;
  }
 
  const scrollToBottom = () => {
    if (messagesEnd.current) {
      messagesEnd.current.scrollIntoView({ behavior: 'smooth' });
    }
  };
 
  const groupBy = function (arr, criteria) {
    return arr.reduce(function (obj, item) {
      // Check if the criteria is a function to run on the item or a property of it
      const key = typeof criteria === 'function' ? criteria(item) : item[criteria];
 
      // If the key doesn't exist yet, create it
      if (!Object.prototype.hasOwnProperty.call(obj, key)) {
        obj[key] = [];
      }
 
      // Push the value to the object
      obj[key].push(item);
 
      // Return the object to the next item in the loop
      return obj;
    }, {});
  };
 
  const objectMap = object => {
    return Object.keys(object).reduce(function (result, key) {
      result.push({ date: key, messages: object[key] });
      return result;
    }, []);
  };
 
  const group = groupBy(messages, datum => dayjs(datum.createdAt).locale(lang).format('dddd, MMMM D, YYYY').toLocaleUpperCase());
  const messageElements = objectMap(group)
    .reverse()
    .map((item, index) => {
      const messageElements = item.messages
        .map(message => {
              return (
                <Message
                  key={uniqueKey}
                  message={message}
                  currentUserID={currentUserID}
                  lang={lang}
                  removeMessage={removeMessage}
                  t={t}
                  chatID={chatID}
                  setSelectedMsg={setSelectedMsg}
                />
              );
        })
        .reverse();
 
      return messageElements;
    })
    .reduce((a, b) => a.concat(b), []);
 
  return (
    <div style={{ marginBottom: '25px' }}>
      <div ref={topElementRef} />
      <div
        style={{
          position: 'relative',
          display: 'flex',
          flexDirection: 'column',
          flexWrap: 'wrap',
          height: '100%',
          overflow: 'hidden',
        }}
        ref={scroller}
      >
        {messageElements}
        <div ref={elementRef} style={{ position: 'absolute', bottom: '5%' }} />
      </div>
    </div>
  );
};
 
export default Messages;

Been stuck on this for 2 weeks lol. Any advice is helpful :)

1

There are 1 answers

10
shubham jha On

Have you tried scrollIntoView ? you can try after changing your autoscroll function like following

const autoscroll = () => {
   elementRef.current.scrollIntoView({ behavior: 'smooth' })
  };