Dun-sin / Whisper

A fun Application to have a random chat with people safely
https://whisper.favour.dev/
MIT License
390 stars 367 forks source link

[OTHER] move the observer into a hook from the messageseen component #669

Closed Dun-sin closed 3 months ago

Dun-sin commented 3 months ago

What would you like to share?

https://github.com/Dun-sin/Whisper/blob/dbec99c68f63c606647ea2cef1d458a72ae83b1b/client/src/components/Chat/MessageSeen.jsx#L29-L68

github-actions[bot] commented 3 months ago

The issue has been unlocked and is now ready for dev. If you would like to work on this issue, you can comment to have it assigned to you.

damirgros commented 3 months ago

Could I take on this issue? I would solve it by creating a new file "useObserver.js" in the folder "hooks":

import useChatUtils from 'src/lib/chatSocket';
import { socket } from 'src/lib/socketConnection';
import { useChat } from 'src/context/ChatContext';
import { useApp } from 'src/context/AppContext';

export default (isSender) => {

    const { seenMessage } = useChatUtils(socket);
    const { receiveMessage } = useChat();
    const { app } = useApp();

    const observer = new IntersectionObserver(
        (entries) => {
            entries.forEach((entry) => {
                if (entry.isIntersecting && !isSender) {
                    // Mark the message as read
                    const messageId = entry.target.getAttribute('id').split('-')[1];
                    try {
                        seenMessage({
                            messageId,
                            chatId: app.currentChatId,
                        });
                    } catch (e) {
                        return;
                    }
                    receiveMessage(messageId, app.currentChatId);
                }
            });
        },
        { threshold: 0.5 }
        // Trigger when 50% of the element is in the viewport
    );
    return observer
};

And I would make adjustments to "MessageSeen.jsx" component:

import React, { useEffect, useMemo } from 'react';
import PropTypes from 'prop-types';

import useIsTabActive from 'src/hooks/useIsTabActive';

import useObserver from 'src/hooks/useObserver';

import { useApp } from 'src/context/AppContext';
import { useChat } from 'src/context/ChatContext';

const MessageSeen = ({ isRead, isSender }) => {
    const { app } = useApp();

    const isTabVisible = useIsTabActive();

    const observer = useObserver(isSender);

    const { messages: state } = useChat();

    const sortedMessages = useMemo(
        () =>
            Object.values(state[app.currentChatId]?.messages ?? {})?.sort((a, b) => {
                const da = new Date(a.time),
                    db = new Date(b.time);
                return da - db;
            }),
        [state, app.currentChatId]
    );

    useEffect(() => {
        // Initialize Intersection Observer
        observer.connect();

        sortedMessages.forEach((message) => {
            if (message.isRead) {
                return;
            }

            const messageElement = document.getElementById(`message-${message.id}`);
            if (messageElement && isTabVisible) {
                observer.observe(messageElement);
            }
        });

        return () => {
            // Clean up the observer
            observer.disconnect();
        };
    }, [sortedMessages, isTabVisible]);

    return isSender && <p className="text-sm">{isRead ? 'Seen' : 'Not Seen'}</p>;
};

export default MessageSeen;

MessageSeen.propTypes = {
    isRead: PropTypes.bool,
    isSender: PropTypes.bool.isRequired,
};

If you want me to, I could memoize the observer hook to prevent unnecessary re-creations whenever component renders.

Dun-sin commented 3 months ago

Could I take on this issue? I would solve it by creating a new file "useObserver.js" in the folder "hooks":

import useChatUtils from 'src/lib/chatSocket';
import { socket } from 'src/lib/socketConnection';
import { useChat } from 'src/context/ChatContext';
import { useApp } from 'src/context/AppContext';

export default (isSender) => {

    const { seenMessage } = useChatUtils(socket);
    const { receiveMessage } = useChat();
    const { app } = useApp();

    const observer = new IntersectionObserver(
        (entries) => {
            entries.forEach((entry) => {
                if (entry.isIntersecting && !isSender) {
                    // Mark the message as read
                    const messageId = entry.target.getAttribute('id').split('-')[1];
                    try {
                        seenMessage({
                            messageId,
                            chatId: app.currentChatId,
                        });
                    } catch (e) {
                        return;
                    }
                    receiveMessage(messageId, app.currentChatId);
                }
            });
        },
        { threshold: 0.5 }
        // Trigger when 50% of the element is in the viewport
    );
    return observer
};

And I would make adjustments to "MessageSeen.jsx" component:

import React, { useEffect, useMemo } from 'react';
import PropTypes from 'prop-types';

import useIsTabActive from 'src/hooks/useIsTabActive';

import useObserver from 'src/hooks/useObserver';

import { useApp } from 'src/context/AppContext';
import { useChat } from 'src/context/ChatContext';

const MessageSeen = ({ isRead, isSender }) => {
  const { app } = useApp();

  const isTabVisible = useIsTabActive();

  const observer = useObserver(isSender);

  const { messages: state } = useChat();

  const sortedMessages = useMemo(
      () =>
          Object.values(state[app.currentChatId]?.messages ?? {})?.sort((a, b) => {
              const da = new Date(a.time),
                  db = new Date(b.time);
              return da - db;
          }),
      [state, app.currentChatId]
  );

  useEffect(() => {
      // Initialize Intersection Observer
      observer.connect();

      sortedMessages.forEach((message) => {
          if (message.isRead) {
              return;
          }

          const messageElement = document.getElementById(`message-${message.id}`);
          if (messageElement && isTabVisible) {
              observer.observe(messageElement);
          }
      });

      return () => {
          // Clean up the observer
          observer.disconnect();
      };
  }, [sortedMessages, isTabVisible]);

  return isSender && <p className="text-sm">{isRead ? 'Seen' : 'Not Seen'}</p>;
};

export default MessageSeen;

MessageSeen.propTypes = {
  isRead: PropTypes.bool,
  isSender: PropTypes.bool.isRequired,
};

If you want me to, I could memoize the observer hook to prevent unnecessary re-creations whenever component renders.

Sure, go for it💪🏽