🔎

URLSearchParamsで絞り込み条件を取り扱う

2024/03/31に公開

Webサイトで作品や商品か何かを絞り込むときにReactなどでは以下のようなメモリを用いて絞り込み条件を管理できます。

const [categories, setCategories] = useState(["a", "b", "c"])
const [colors, setColors] = useState(["red", "green", "blue"])

しかし、検索条件を共有したい場合はこのようにURLにしておいた方が便利なこともあります。

http://localhost:3000/works?categories=a.b.c&colors=red.green.blue

この値はURLSearchParamsを用いればこのように取得できます。

const text = searchParams.get(key)
const currentItems = text?.split(".") ?? []

URLSearchParamsの使い方はこちら。

https://developer.mozilla.org/ja/docs/Web/API/URLSearchParams

例えば、Remixではこのように取得できます。

import { useSearchParams } from "@remix-run/react"

const [searchParams, setSearchParams] = useSearchParams()

条件を取得する

このようにしてURLから取得します。

const filter = {
  colors: searchParams.get("colors")?.split(".") ?? [],
  categories: searchParams.get("categories")?.split(".") ?? [],
}

ReactではこのようなHooksを定義しておけばコンポーネントから条件を参照できそうです。

ここでは react-router-dom に依存してしまっているのでNext.jsなど他の環境で使用するには少し修正が必要です。

import type { SetURLSearchParams } from "react-router-dom"

export function useWorkListFilter(
  searchParams: URLSearchParams,
  setSearchParams: SetURLSearchParams,
) {
  return {
    colors: searchParams.get("colors")?.split(".") ?? [],
    categories: searchParams.get("categories")?.split(".") ?? [],
  }
}

コンポーネントではこのように使用します。

exort function MyComponent() {
  const [searchParams, setSearchParams] = useSearchParams()

  const filter = useProductListFilter(searchParams, setSearchParams)

  return null
}

条件を追加する

このような関数を定義できます。

export function addItemToSearchParams(
  searchParams: URLSearchParams,
  key: string,
  payload: string,
): URLSearchParams {
  const text = searchParams.get(key)

  const items = text ? text.split(".") : []

  if (items.includes(payload)) {
    items.splice(items.indexOf(payload), 1)
  } else {
    items.push(payload)
  }

  if (items.length === 0) {
    searchParams.delete(key)
  }

  if (items.length === length) {
    searchParams.delete(key)
  }

  if (items.length !== length && items.length !== 0) {
    searchParams.set(key, items.join("."))
  }

  return searchParams
}

このように使用できます。

const addColor = (payload: string) => {
  const draft = addItemToSearchParams(searchParams, "colors", payload)
  setSearchParams(draft, { replace: true, preventScrollReset: true })
}

RemixではpreventScrollResetを設定することでスクロールが上に戻るのを阻止できます。

全ての条件を追加または削除する

このようなチェックボックスを実装する場合は要素を全て追加するか削除する必要があります。

  • 全て
  • 赤色
  • 緑色
  • 青色

そこでこのような関数を定義します。

export function addAllItemsToSearchParams(
  searchParams: URLSearchParams,
  key: string,
  allItems: string[],
): URLSearchParams {
  const text = searchParams.get(key)

  const currentItems = text?.split(".") ?? []

  if (currentItems.length === length) {
    searchParams.delete(key)
  }

  if (currentItems.length !== 0) {
    searchParams.delete(key)
  }

  return searchParams
}

このように使用できます。全ての要素は設定などどこか別の箇所で管理しておきます。

const addAllColors = () => {
  const allItems = appConfig.product.colors.map((item) => item.slug)
  const draft = addAllItemsToSearchParams(
    searchParams,
    "colors",
    allItems,
  )
  setSearchParams(draft, { replace: true, preventScrollReset: true })
}

Hooksを修正する

先ほどの関数もHooksの中で定義しておくことができます。

export function useWorkListFilter(
  searchParams: URLSearchParams,
  setSearchParams: SetURLSearchParams,
) {
  return {
    colors: searchParams.get("colors")?.split(".") ?? [],
    categories: searchParams.get("categories")?.split(".") ?? [],
    addColor: addColors,
    addAllColor: addAllColors,
  }
}

このように使用できます。

const filter = useProductListFilter(searchParams, setSearchParams)

return (
  <ProductColorFilter
    values={filter.colors}
    onCheck={filter.addColor}
    onCheckAll={filter.addAddColors}
  />
)
Aipictors

Discussion