All files / src/features/GiveConversation/hooks useConversationSearch.ts

94.59% Statements 35/37
68.42% Branches 13/19
81.81% Functions 9/11
94.28% Lines 33/35

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118                                              6x         52x         52x 52x   52x 12x     52x 11x 5x   6x         52x 52x 48x   4x 4x         4x 20x         4x       52x 10x 1x     1x 1x         52x   2x 2x   2x           2x 2x           52x 2x     52x         52x                    
import { useState, useMemo, useCallback, useEffect } from "react";
import { MessagesArrayTypes } from "../types";
import { countMatchingMessages } from "../utils";
import {
  GiveConversationSections,
  GiveConversationSectionType,
} from "../constants";
 
/**
 * A custom hook that provides search functionality for conversation messages.
 * It allows searching through messages, tracking matches, and navigating between them.
 * @param sectionInView - The current section in view
 * @param singleThreadMessages - Optional array of message threads to search through
 * @param threadData - The thread data displayed in the main view
 * @returns An object containing:
 *   - searchState: combines the current search term and the search opened state
 *   - setSearchState: Function to update the search term and the search opened state
 *   - matchingMessagesCount: Total number of messages matching the search
 *   - currentMatchIndex: Index of the currently selected match
 *   - setCurrentMatchIndex: Function to update the current match index
 *   - navigateToNextMatch: Function to navigate to the next matching message
 *   - navigateToPreviousMatch: Function to navigate to the previous matching message
 */
export const useConversationSearch = (
  sectionInView: GiveConversationSectionType,
  singleThreadMessages: MessagesArrayTypes[],
  threadData: any,
) => {
  const [searchState, setSearchState] = useState({
    searchValue: "",
    isOpened: false,
  });
 
  const [currentMatchIndex, setCurrentMatchIndex] = useState(0);
  const { searchValue } = searchState;
  // Reset the current match index whenever the search value changes
  useEffect(() => {
    setCurrentMatchIndex(0);
  }, [searchValue, sectionInView]);
 
  const messageIdPrefix = useMemo(() => {
    if (sectionInView === GiveConversationSections.MESSAGE_CHATS) {
      return "thread-message-";
    }
    return "thread-";
  }, [sectionInView]);
 
  // Calculate matching messages and their IDs based on the search value
  const { numberOfMatches: matchingMessagesCount, matchingMessageIds } =
    useMemo(() => {
      if ((!singleThreadMessages && !threadData) || !searchValue)
        return { numberOfMatches: 0, matchingMessageIds: [] };
      const isThreadListView =
        sectionInView === GiveConversationSections.MESSAGE_LIST;
      const arrayToUse = isThreadListView
        ? threadData
        : singleThreadMessages.flatMap((item) => item.messages);
 
      // In thread list view we search in the title, in message chats we search in the message
      const searchField = isThreadListView ? "title" : "message";
      const arrayOfMessages = arrayToUse?.map((item: any) => ({
        message: item[searchField],
        id: item.id,
      }));
 
      return countMatchingMessages(arrayOfMessages, searchValue);
    }, [singleThreadMessages, threadData, searchValue, sectionInView]);
 
  //scroll to first match if there is any, imediatelly when we have a match
  useEffect(() => {
    if (matchingMessagesCount) {
      const firstMatch = document.getElementById(
        `${messageIdPrefix}${matchingMessageIds[0]}`,
      );
      Eif (firstMatch) {
        firstMatch.scrollIntoView({ behavior: "smooth" });
      }
    }
  }, [matchingMessagesCount]);
 
  const navigateToMatch = useCallback(
    (direction: "next" | "previous") => {
      const increment = direction === "next" ? 1 : -1;
      setCurrentMatchIndex((prev) => prev + increment);
 
      const element = document.getElementById(
        `${messageIdPrefix}${
          matchingMessageIds[currentMatchIndex + increment]
        }`,
      );
 
      Eif (element) {
        element.scrollIntoView({ behavior: "smooth" });
      }
    },
    [currentMatchIndex, matchingMessageIds, messageIdPrefix],
  );
 
  const navigateToNextMatch = useCallback(
    () => navigateToMatch("next"),
    [navigateToMatch],
  );
  const navigateToPreviousMatch = useCallback(
    () => navigateToMatch("previous"),
    [navigateToMatch],
  );
 
  return {
    searchState,
    setSearchState,
    matchingMessagesCount,
    currentMatchIndex,
    setCurrentMatchIndex,
    navigateToNextMatch,
    navigateToPreviousMatch,
  };
};