'Detect only first change/render of a ref in react and scroll to bottom
DESIRED RESULT: I am trying to create a chat panel that has a scrollable container which contains all the messages. The container has an initial loading state, and when the data for that container is fetched then the container is rendered. What I am trying to achieve is that, when the container first renders after the loading is complete it will scroll to the bottom of the container. After that any change on that container won't interfere with the scroll. Only when a new message is sent, a socket event will be fired and it will trigger the container again to scroll to bottom.
PROBLEM: When giving react-query's isFethcedAfterMount and a socket event messageCreated as the dependency of useEffect() it works, but isFethcedAfterMount always changes whenever incoming or outgoing message is sent and the API is refetched and the container scrolls to bottom. This will hamper user experience as whenever he/she is scrolling through old messages any new message coming will scroll the container to bottom.
So What I want is that the chat container scrolls to bottom when the container first renders or the ref of the container only changes for the first time and after this change is detected then only messageCreated event will affect the scrollToBottom function.
Here's the code that I am using to implement all of the logic. Also tried to comment each section for better understanding. Please let me know if I missed something.
// check ref and scroll to the ref's bottom
const scrollToBottom = (ref) => {
if (ref) {
ref.current?.addEventListener("DOMNodeInserted", (event) => {
const { currentTarget: target } = event;
target.scroll({ top: target.scrollHeight });
});
}
};
// main component
const MessageList = () => {
const router = useRouter();
const conversationId = +router.query.conversationId;
// react query to get data from API and a loading state
const { isLoading, data: messageData, isFetchedAfterMount } = useQuery(["conversation-messages", conversationId], () => getMessages(conversationId), {
refetchOnWindowFocus: false,
enabled: !!conversationId,
staleTime: Infinity,
});
// declaring ref of the message list container
const messagesEndRef = useRef(null);
// get the event when a message is sent
const messageCreated = useConversationStore((state) => state.messageCreated);
useEffect(() => {
if (isFetchedAfterMount) scrollToBottom(messagesEndRef);
}, [isFetchedAfterMount, messageCreated]);
// messageCreated dependency triggers scrollToBottom each time it changes
// isFetchedAfterMount dependency triggers the useEffect hook to render on first mount
// but the problem of isFetchedAfterMount is that it changes each time new data is fetched from API
return isLoading ? (
<div className="w-full h-full flex justify-center items-center">
<ClipLoader color="#38bdf8" />
</div>
) : (
// after loading finishes get the ref of this message list container and scroll to it's bottom
<ul className="flex flex-col flex-grow pt-1 overflow-y-auto" ref={measuredRef}>
<li className="min-h-16 flex justify-center items-center">*</li>
<li className="min-h-16 flex justify-center items-center">*</li>
<li className="min-h-16 flex justify-center items-center">*</li>
<li className="min-h-16 flex justify-center items-center">*</li>
<li className="min-h-16 flex justify-center items-center">*</li>
<li className="min-h-16 flex justify-center items-center">*</li>
<li className="min-h-16 flex justify-center items-center">*</li>
<li className="min-h-16 flex justify-center items-center">*</li>
// ........ more message .........
</ul>
);
};
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
