sparkletown/sparkle

View on GitHub
src/components/templates/ScreeningRoom/useScreeningRoom.ts

Summary

Maintainability
C
7 hrs
Test Coverage
import { useCallback, useMemo, useState } from "react";
import Fuse from "fuse.js";

import { DEFAULT_DISPLAYED_VIDEO_PREVIEW_COUNT } from "settings";

import { screeningRoomVideosSelector } from "utils/selectors";
import { isTruthy } from "utils/types";

import { useDebounceSearch } from "hooks/useDebounceSearch";
import { isLoaded, useFirestoreConnect } from "hooks/useFirestoreConnect";
import { useSelector } from "hooks/useSelector";

const emptyScreeningRoomVideosArray: never[] = [];

export const useConnectScreeningRoomVideos = (screeningRoomVenueId: string) => {
  useFirestoreConnect([
    {
      collection: "venues",
      doc: screeningRoomVenueId,
      subcollections: [{ collection: "screeningRoomVideos" }],
      storeAs: "screeningRoomVideos",
    },
  ]);
};

export const useScreeningRoomVideos = (screeningRoomVenueId: string) => {
  useConnectScreeningRoomVideos(screeningRoomVenueId);

  const screeningRoomVideos = useSelector(screeningRoomVideosSelector);

  return useMemo(
    () => ({
      screeningRoomVideos: screeningRoomVideos ?? emptyScreeningRoomVideosArray,
      isScreeningRoomVideosLoaded: isLoaded(screeningRoomVideos),
    }),
    [screeningRoomVideos]
  );
};

export const useScreeningRoom = (screeningRoomVenueId: string) => {
  const {
    screeningRoomVideos,
    isScreeningRoomVideosLoaded: isVideosLoaded,
  } = useScreeningRoomVideos(screeningRoomVenueId);

  const {
    searchInputValue,
    searchQuery,
    setSearchInputValue,
  } = useDebounceSearch();

  const [categoryFilter, _setCategoryFilter] = useState<string>();
  const [subCategoryFilter, setSubCategoryFilter] = useState<string>();
  const [displayedVideosAmount, setDisplayedVideosAmount] = useState(
    DEFAULT_DISPLAYED_VIDEO_PREVIEW_COUNT
  );

  const setCategoryFilter = useCallback((category: string) => {
    // Clear previously chosen subcategory
    setSubCategoryFilter(undefined);
    _setCategoryFilter(category);
  }, []);

  const unsetCategoryFilter = useCallback(() => {
    // Clear chosen subcategory, if any
    setSubCategoryFilter(undefined);
    _setCategoryFilter(undefined);
  }, []);

  const increaseDisplayedVideosAmount = useCallback(() => {
    setDisplayedVideosAmount(
      (prevVideosNumber) =>
        prevVideosNumber + DEFAULT_DISPLAYED_VIDEO_PREVIEW_COUNT
    );
  }, []);

  const categoryList = useMemo(
    () =>
      Array.from(new Set(screeningRoomVideos.map((video) => video.category))),
    [screeningRoomVideos]
  );

  const filteredVideosByCategory = useMemo(
    () =>
      categoryFilter
        ? screeningRoomVideos.filter(
            (video) => video.category === categoryFilter
          )
        : screeningRoomVideos,
    [screeningRoomVideos, categoryFilter]
  );

  const subCategoryList = useMemo(
    () =>
      categoryFilter
        ? Array.from(
            new Set(filteredVideosByCategory.map((video) => video.subCategory))
          ).filter(isTruthy)
        : [],
    [filteredVideosByCategory, categoryFilter]
  );

  const filteredVideosBySubCategory = useMemo(
    () =>
      subCategoryFilter
        ? filteredVideosByCategory.filter(
            (video) => video.subCategory === subCategoryFilter
          )
        : filteredVideosByCategory,
    [filteredVideosByCategory, subCategoryFilter]
  );

  const fuseVideos = useMemo(
    () =>
      new Fuse(filteredVideosBySubCategory, {
        keys: [
          "title",
          {
            name: "authorName",
            weight: 8,
          },
          "introduction",
        ],
        threshold: 0.2, // 0.1 seems to be exact, default 0.6: brings too distant if anyhow related hits
        ignoreLocation: true, // default False: True - to search ignoring location of the words.
        findAllMatches: true,
      }),
    [filteredVideosBySubCategory]
  );

  const searchedVideos = useMemo(() => {
    if (!searchQuery) return filteredVideosBySubCategory;

    return fuseVideos
      .search(searchQuery)
      .map((fuseSearchItem) => fuseSearchItem.item);
  }, [searchQuery, fuseVideos, filteredVideosBySubCategory]);

  const displayedVideos = useMemo(
    () =>
      searchedVideos
        // As per https://stackoverflow.com/questions/53420055/error-while-sorting-array-of-objects-cannot-assign-to-read-only-property-2-of/53420326
        .slice()
        .sort((a, b) => a.title.localeCompare(b.title))
        .slice(0, displayedVideosAmount),
    [searchedVideos, displayedVideosAmount]
  );

  const hasHiddenVideos = searchedVideos.length > displayedVideos.length;

  return {
    videos: displayedVideos,
    isVideosLoaded,

    hasHiddenVideos,

    categoryList,
    subCategoryList,

    searchInputValue,
    categoryFilter,
    subCategoryFilter,

    increaseDisplayedVideosAmount,
    setSearchInputValue,
    setCategoryFilter,
    setSubCategoryFilter,
    unsetCategoryFilter,
  };
};