見出し画像

useSearchBarというカスタムフックを作って上下矢印キー操作からアイテムを操作してみよう

こんにちわ。nap5です。


useSearchBarというカスタムフックを作って上下矢印キー操作からアイテムを操作してみたので紹介したいと思います。


前回のカスタムフックの記事と関連しています。


少し前ですが、以下の検索バーの記事とも関連です。


デモサイトです。上下矢印キーでアイテム選択後、選択した状態でEnterキーを押下すると、今回のデモだと別ページへ遷移します。


以下はコードの抜粋になります。まずはストア側の定義なります。

アイテムのアクティブインデックスが保持できればOKです。

import { atom } from 'recoil'
import { z } from 'zod'

const SearchBarSchema = z.object({
  activeIndex: z.number(),
})

export type SearchBar = z.infer<typeof SearchBarSchema>

const searchBarState = atom<SearchBar>({
  key: 'searchBar',
  default: {
    activeIndex: 0,
  },
})

export { searchBarState }



次にフック側です。react-useライブラリのuseKeyPressからポートしてキーダウンとアップ、そしてEnterキーに対してアクションを定義しています。


アイテム選択時にEnterキーを押下すると、今回のデモだと別ページへ遷移します。


import { useEffect, useMemo } from 'react'

import { useRouter } from 'next/router'

import { useKeyPress } from 'react-use'
import { useRecoilValue, useSetRecoilState } from 'recoil'

import { searchBarState } from '@/features/film/stores/searchBar'
import { FilmsData } from '@/features/film/types'

const useSearchBar = (data: FilmsData) => {
  const router = useRouter()
  const setSearchBar = useSetRecoilState(searchBarState)
  const activeSearchBar = useRecoilValue(searchBarState)

  // https://github.com/streamich/react-use/blob/master/docs/useKeyPress.md
  const [isPressedArrowDown] = useKeyPress('ArrowDown')
  const [isPressedArrowUp] = useKeyPress('ArrowUp')
  const [isPressedEnter] = useKeyPress('Enter')
  const [isPressedEscape] = useKeyPress('Escape')

  const maxSize = useMemo(() => {
    if (!data) {
      return 0
    }
    return data.length
  }, [data])

  const { activeIndex } = useMemo(() => {
    return { ...activeSearchBar }
  }, [activeSearchBar])

  useEffect(() => {
    if (isPressedEnter) {
      if (activeIndex !== null && activeIndex !== undefined && data) {
        router.push({
          pathname: `/films/${data[activeIndex]?.id}`,
        })
      }
    }
  }, [isPressedEnter, activeIndex, data, router])

  useEffect(() => {
    if (!isPressedArrowUp) {
      return
    }
    setSearchBar((prevState) => {
      return {
        activeIndex:
          prevState.activeIndex !== 0 ? prevState.activeIndex - 1 : maxSize - 1,
      }
    })
  }, [isPressedArrowUp, setSearchBar, maxSize])

  useEffect(() => {
    if (!isPressedArrowDown) {
      return
    }
    setSearchBar((prevState) => {
      return {
        activeIndex:
          prevState.activeIndex !== maxSize - 1 ? prevState.activeIndex + 1 : 0,
      }
    })
  }, [isPressedArrowDown, maxSize, setSearchBar])

  return useMemo(() => {
    return {
      activeIndex,
      isPressedArrowDown,
      isPressedArrowUp,
      isPressedEnter,
      isPressedEscape,
    }
  }, [
    activeIndex,
    isPressedArrowDown,
    isPressedArrowUp,
    isPressedEnter,
    isPressedEscape,
  ])
}

export default useSearchBar


最後にコンポーネント側でアクションインデックスを参照しておしまいです。


const Films = () => {
  const { data, error, refetch } = useListUpFilmHook()
  const { activeIndex } = useSearchBar(data)
  return null
}
export default Films


いろいろ応用してみると使いどころがあると思います。


簡単ですが、以上です。


この記事が気に入ったらサポートをしてみませんか?