Untitled

mail@pastecode.io avatar
unknown
javascript
2 years ago
4.7 kB
2
Indexable
Never
import React, { useCallback, useEffect, useRef, useState } from "react";
import styles from "./AutosuggestionSelect.module.css";

const AutosuggestionSelect = ({
  title,
  endpoint,
  onItemSelect,
  propertyKey,
  toggle,
}) => {
  const [openBox, setOpenBox] = useState(false);
  const [rawOptions, setRawOptions] = useState([]);
  const [selectedItems, setSelectedItems] = useState([]);
  const [optionsToDisplay, setOptionsToDisplay] = useState([]);
  const [search, setSearch] = useState("");
  const wrapperRef = useRef(null);
  const searchInputRef = useRef();

  const prepareOptions = () => {
    if (!selectedItems?.length) setOptionsToDisplay(rawOptions);
    if (!rawOptions?.length) setOptionsToDisplay(selectedItems);
    const preparedOptions = rawOptions?.filter(
      (option) => !selectedItems.includes(option)
    );
    setOptionsToDisplay([...selectedItems, ...preparedOptions]);
  };

  const debounce = (fn, timeout = 350) => {
    let timer;
    return (...args) => {
      clearTimeout(timer);
      timer = setTimeout(() => {
        fn(...args);
      }, timeout);
    };
  };

  const onHide = () => {
    setRawOptions([]);
    setSearch([]);
    setOpenBox(false);
  };

  const handleResetButtonClick = () => {
    setSelectedItems([]);
    onItemSelect("", []);
  };

  const onMainButtonClick = () => {
    setOpenBox((prev) => !prev);
    if (openBox) onHide();
  };

  const getData = (query, propertyKeyArg) => {
    if (!query.length) return setRawOptions([]);
    fetch(endpoint + query)
      .then((response) => response.json())
      .then((data) => {
        const mappedData = data.map((item) => item[propertyKeyArg]);
        setRawOptions(mappedData);
      });
  };

  const getDebouncedData = useCallback(
    debounce((search, propertyKey) => getData(search, propertyKey)),
    []
  );

  const handleOptionClick = (e) => {
    const item = e.target.innerHTML;
    setSelectedItems((prev) => {
      const selectedItemsArray = prev.includes(item)
        ? prev.filter((i) => i !== item)
        : [...prev, item];
      onItemSelect(item, selectedItemsArray);
      return selectedItemsArray;
    });
  };

  const handleOutsideClick = (e) => {
    if (e.target.className.includes("AutosuggestionSelect_optionItem")) return;
    if (wrapperRef.current && !wrapperRef.current.contains(e.target)) {
      onHide();
    }
  };

  const handleEscape = (e) => {
    if (e.key && e.key === "Escape") onHide();
  };

  useEffect(() => {
    if (searchInputRef.current) searchInputRef.current.focus();
  }, [openBox]);

  useEffect(() => {
    getDebouncedData(search, propertyKey);
  }, [search]);

  useEffect(() => {
    prepareOptions();
  }, [rawOptions, selectedItems]);

  useEffect(() => {
    document.addEventListener("click", handleOutsideClick);
    document.addEventListener("keydown", handleEscape);
    return () => {
      document.removeEventListener("click", handleOutsideClick);
      document.removeEventListener("keydown", handleEscape);
    };
  }, []);

  return (
    <>
      <div ref={wrapperRef}>
        <div className={styles.title} onClick={onMainButtonClick}>
          {title}{" "}
          <span className={styles.counter}>{selectedItems.length} </span>
        </div>
        {openBox && (
          <div className={styles.selectWrapper}>
            <div className={styles.inputContainer}>
              <input
                ref={searchInputRef}
                placeholder="Search..."
                className={styles.inputField}
                type="text"
                value={search}
                onChange={(e) => {
                  setSearch(e.target.value);
                }}
              />
            </div>
            <div className={styles.optionsContainer}>
              {optionsToDisplay.map((option, idx) => (
                <div
                  onClick={handleOptionClick}
                  key={idx}
                  className={`${styles.optionItem} ${
                    selectedItems.includes(option)
                      ? styles.optionSelected
                      : null
                  }`}
                >
                  {option}
                </div>
              ))}
            </div>
            {selectedItems.length ? (
              <button
                onClick={handleResetButtonClick}
                className={styles.resetButton}
              >
                Reset
              </button>
            ) : null}
          </div>
        )}
      </div>
    </>
  );
};

export default AutosuggestionSelect;