hooks

use-choice-state

back

Preview

{"selectedItems":[]}

Source code

import { useCallback, useRef, useState } from "react";

export type UseChoiceStateOptions<TData = unknown, TItem = TData> = {
  select: (item: TData) => TItem;
  getId: (item: TItem) => number;
  shouldSelect: (item: TData) => boolean;
};

export const useChoiceState = <TData = unknown, TItem = TData>(
  items: TData[],
  { select, shouldSelect, getId }: UseChoiceStateOptions<TData, TItem>
) => {
  const selectRef = useRef(select);
  const getIdRef = useRef(getId);
  const shouldSelectRef = useRef(shouldSelect);

  const [selectedItems, setSelectedItems] = useState<TData[]>([]);

  const selectedItemIds = selectedItems.map(selectRef.current);
  const areAllItemsSelected =
    selectedItems.length === items.filter(shouldSelectRef.current).length;

  const selectAll = useCallback(
    (checked: boolean) => {
      if (checked) {
        setSelectedItems(items.filter(shouldSelectRef.current));
      } else {
        setSelectedItems([]);
      }
    },
    [items]
  );

  const selectById = useCallback((item: TData, checked: boolean) => {
    const id = getIdRef.current(selectRef.current(item));

    if (!shouldSelectRef.current(item)) return;

    if (checked) {
      setSelectedItems((prev) => [...prev, item]);
    } else {
      setSelectedItems((prev) =>
        prev.filter(
          (_item) => getIdRef.current(selectRef.current(_item)) !== id
        )
      );
    }
  }, []);

  const clearSelectedItems = useCallback(() => {
    setSelectedItems([]);
  }, []);

  const getIsItemSelected = useCallback(
    (item: TItem) => {
      const isSelected =
        selectedItems.findIndex(
          (_item) =>
            getIdRef.current(selectRef.current(_item)) ===
            getIdRef.current(item)
        ) !== -1;

      return isSelected;
    },
    [selectedItems]
  );

  return {
    selectedItemIds,
    selectedItems,
    areAllItemsSelected,
    selectAll,
    selectById,
    clearSelectedItems,
    getIsItemSelected,
  };
};