# All files / src/TieredDropdown useSearch.ts

40% Statements 12/30
12.5% Branches 1/8
22.22% Functions 2/9
35.71% Lines 10/28

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
    942x

    2x

    2x

    4x

    4x
    4x
    4x

    4x

    4x

    4x

     import { useCallback, useMemo, useState } from 'react'

    // eslint-disable-next-line no-useless-escape
    const periodDashAndWhitespaceRegex = /[\.\-\s]/g

    function matchString(value: string, searchQuery: string) {
      const lowerCasedKeyValue = (value && value.toLowerCase()) || ''

      const valueWithoutSeparators = lowerCasedKeyValue.replace(
        periodDashAndWhitespaceRegex,
        ''
      )

      return valueWithoutSeparators.includes(searchQuery)
    }

    function matchKeyInObject<T extends Record<string, any>, P extends keyof T>(
      item: T,
      key: P,
      searchQuery: string
    ): boolean {
      Iif (typeof item[key] === 'string') {
        return matchString(item[key] as string, searchQuery)
      }

      Iif (typeof item[key] === 'number') {
        return matchString(`${item[key]}`, searchQuery)
      }

      return false
    }

    function filterCondition<T extends Record<string, any>, P extends keyof T>(
      item: T,
      IsearchTerm: string = '',
      IkeysToMatch: P[] = Object.keys(item) as P[]
    ) {
      const lowerCasedSearchTerm = searchTerm.trim().toLowerCase()
      const sanitizedSearchQuery = lowerCasedSearchTerm.replace(
        periodDashAndWhitespaceRegex,
        ''
      )

      return keysToMatch.some((key) =>
        matchKeyInObject(item, key, sanitizedSearchQuery)
      )
    }

    interface SearchConfig<T> {
      items: T[]
      keysToSearch?: string[]
    }

    interface SearchApi<T> {
      searchTerm: string
      clearSearch: () => void
      filteredItems: T[]
      setSearch: (value: string) => void
    }

    export function useSearch<T extends Record<string, any>>(
      config: SearchConfig<T>
    ): SearchApi<T> {
      const [searchTerm, setSearchTerm] = useState('')

      const filteredItems = useMemo(() => {
        if (searchTerm === '') {
          return config.items
        }

        return config.items.filter((item) =>
          filterCondition(item, searchTerm, config.keysToSearch)
        )
      }, [config, searchTerm])

      const setSearch = useCallback(
        (value: string) => {
          setSearchTerm(value)
        },
        [setSearchTerm]
      )

      const clearSearch = useCallback(() => {
        setSearchTerm('')
      }, [setSearchTerm])

      return {
        clearSearch,
        filteredItems,
        setSearch,
        searchTerm,
      }
    }