From abe14c27b92fcdc732e3ce0695dbde39319a1ab8 Mon Sep 17 00:00:00 2001 From: kohii Date: Sun, 10 Mar 2024 15:00:45 +0900 Subject: [PATCH] fix: some search params is not preserved on search --- src/app/s/search-result.tsx | 8 +-- .../shinryoukoui-master-data-context.tsx | 7 +-- .../advanced-search-button.tsx | 4 +- .../display-fields/display-fields-modal.tsx | 27 ++++------ .../display-fields/use-display-fields.ts | 2 +- src/features/search/search-bar.tsx | 4 +- src/features/search/search-input.tsx | 4 +- .../shinryoukoui-master-versions/constants.ts | 2 - src/hooks/use-shinryoukoui-search.tsx | 49 ++++++++++++++----- src/search-param-names.ts | 8 ++- 10 files changed, 68 insertions(+), 47 deletions(-) diff --git a/src/app/s/search-result.tsx b/src/app/s/search-result.tsx index fd7013c..bd899cd 100644 --- a/src/app/s/search-result.tsx +++ b/src/app/s/search-result.tsx @@ -20,10 +20,10 @@ import { parseQuery } from "@/features/search/parse-query"; import { SearchBar, SearchBarHandle } from "@/features/search/search-bar"; import { getField } from "@/features/shinryoukoui-master-fields/shinryoukoui-master-fields"; import { VersionSelect } from "@/features/shinryoukoui-master-versions/version-select"; -import { useShinryoukouiSearch } from "@/hooks/use-shinryoukoui-search"; +import { useShinryoukouiSearchByQuery } from "@/hooks/use-shinryoukoui-search"; import { useStateFromProp } from "@/hooks/use-state-from-props"; import { useUpdateSearchParams } from "@/hooks/use-update-search-params"; - +import { SEARCH_PARAM_NAMES } from "@/search-param-names"; import { Detail } from "./detail"; import { useTableColumns } from "./use-table-columns"; @@ -33,7 +33,7 @@ const nameField = getField("診療行為省略名称/省略漢字名称"); export default function SearchResult() { const searchParams = useSearchParams(); - const query = searchParams.get("q") ?? ""; + const query = searchParams.get(SEARCH_PARAM_NAMES.SEARCH.QUERY) ?? ""; const selectedCode = searchParams.get("code"); const updateSearchParams = useUpdateSearchParams(); @@ -53,7 +53,7 @@ export default function SearchResult() { const [searchInputValue, setSearchInputValue] = useStateFromProp(query ?? ""); const [advancedSearchOpen, setAdvancedSearchOpen] = useState(false); - const search = useShinryoukouiSearch(); + const search = useShinryoukouiSearchByQuery(); const filterExpression = useMemo(() => { const r = parseQuery(query); diff --git a/src/contexts/shinryoukoui-master-data-context.tsx b/src/contexts/shinryoukoui-master-data-context.tsx index 107e6d2..9181ea5 100644 --- a/src/contexts/shinryoukoui-master-data-context.tsx +++ b/src/contexts/shinryoukoui-master-data-context.tsx @@ -5,8 +5,9 @@ import React, { createContext, useContext, useMemo } from "react"; import { fetchMasterData } from "@/apis/fetch-master-data"; import { getValue } from "@/features/fields/get-values"; import { getField } from "@/features/shinryoukoui-master-fields/shinryoukoui-master-fields"; -import { LATEST_SHINRYOUKOUI_MASTER_VERSION, MASTER_VERSION_SEARCH_PARAM_NAME, SHINRYOUKOUI_MASTER_VERSION_KEYS } from "@/features/shinryoukoui-master-versions/constants"; +import { LATEST_SHINRYOUKOUI_MASTER_VERSION, SHINRYOUKOUI_MASTER_VERSION_KEYS } from "@/features/shinryoukoui-master-versions/constants"; import { useUpdateSearchParams } from "@/hooks/use-update-search-params"; +import { SEARCH_PARAM_NAMES } from "@/search-param-names"; type ShinryoukouiMasterDataContextType = { version: string; @@ -33,7 +34,7 @@ export function useShinryoukouiMasterData() { export function ShinryoukouiMasterDataProvider({ children }: { children: React.ReactNode }) { const searchParams = useSearchParams(); const updateSearchParams = useUpdateSearchParams(); - const paramVersion = searchParams.get(MASTER_VERSION_SEARCH_PARAM_NAME); + const paramVersion = searchParams.get(SEARCH_PARAM_NAMES.SEARCH.MASTER_VERSION); const version = paramVersion && SHINRYOUKOUI_MASTER_VERSION_KEYS.includes(paramVersion) ? paramVersion : LATEST_SHINRYOUKOUI_MASTER_VERSION; @@ -59,7 +60,7 @@ export function ShinryoukouiMasterDataProvider({ children }: { children: React.R return { version, setVersion(version: string) { - updateSearchParams({ [MASTER_VERSION_SEARCH_PARAM_NAME]: version === LATEST_SHINRYOUKOUI_MASTER_VERSION ? undefined : version }); + updateSearchParams({ [SEARCH_PARAM_NAMES.SEARCH.MASTER_VERSION]: version === LATEST_SHINRYOUKOUI_MASTER_VERSION ? undefined : version }); }, data: data ?? [], isLoading, diff --git a/src/features/advanced-search/advanced-search-button.tsx b/src/features/advanced-search/advanced-search-button.tsx index 8d628b4..df713d2 100644 --- a/src/features/advanced-search/advanced-search-button.tsx +++ b/src/features/advanced-search/advanced-search-button.tsx @@ -4,7 +4,7 @@ import dynamic from "next/dynamic"; import { useState } from "react"; import { Backdrop } from "@/components/backdrop"; -import { useShinryoukouiSearch } from "@/hooks/use-shinryoukoui-search"; +import { useShinryoukouiSearchByQuery } from "@/hooks/use-shinryoukoui-search"; type AdvancedSearchLinkProps = { initialQuery?: string; @@ -17,7 +17,7 @@ export function AdvancedSearchButton({ initialQuery }: AdvancedSearchLinkProps) setAdvancedSearchOpen(true); }; - const search = useShinryoukouiSearch(); + const search = useShinryoukouiSearchByQuery(); return ( <> diff --git a/src/features/display-fields/display-fields-modal.tsx b/src/features/display-fields/display-fields-modal.tsx index 3e8ee5d..0f25acf 100644 --- a/src/features/display-fields/display-fields-modal.tsx +++ b/src/features/display-fields/display-fields-modal.tsx @@ -1,8 +1,8 @@ -import { useRouter } from "next/navigation"; import { useCallback, useState } from "react"; import { Button } from "@/components/button"; import { Modal } from "@/components/modal"; +import { useUpdateSearchParams } from "@/hooks/use-update-search-params"; import { SEARCH_PARAM_NAMES } from "@/search-param-names"; import { DEFAULT_DISPLAY_FIELDS } from "./constants"; @@ -15,6 +15,8 @@ type DisplayFieldsModalProps = { onClose: () => void; } +const DEFAULT_DISPLAY_FIELDS_STRING = stringifyDisplayFieldConfigs(DEFAULT_DISPLAY_FIELDS); + export function DisplayFieldsModal({ fields: initialFields, onClose, @@ -24,26 +26,15 @@ export function DisplayFieldsModal({ id: crypto.randomUUID(), }))); - const { push } = useRouter(); - + const updateSearchParams = useUpdateSearchParams(); const handleOk = useCallback(() => { - const searchParams = new URLSearchParams(location.search); - if (fields.length === 0) { - searchParams.delete(SEARCH_PARAM_NAMES.FIELDS); - } else { - const s = stringifyDisplayFieldConfigs(fields); - const defaults = stringifyDisplayFieldConfigs(DEFAULT_DISPLAY_FIELDS); - if (s === defaults) { - searchParams.delete(SEARCH_PARAM_NAMES.FIELDS); - } else { - searchParams.set(SEARCH_PARAM_NAMES.FIELDS, s); - } - } - - push(`/s?${searchParams.toString()}`); + const s = stringifyDisplayFieldConfigs(fields); + updateSearchParams({ + [SEARCH_PARAM_NAMES.SEARCH.FIELDS]: (!s || s === DEFAULT_DISPLAY_FIELDS_STRING) ? undefined : s, + }); onClose(); - }, [fields, onClose, push]); + }, [fields, onClose, updateSearchParams]); const handleReset = () => { setFields(DEFAULT_DISPLAY_FIELDS.map((field) => ({ diff --git a/src/features/display-fields/use-display-fields.ts b/src/features/display-fields/use-display-fields.ts index 1cece14..c418841 100644 --- a/src/features/display-fields/use-display-fields.ts +++ b/src/features/display-fields/use-display-fields.ts @@ -9,7 +9,7 @@ import { DisplayFieldConfig } from "./types"; export function useDisplayFieldConfigs(): DisplayFieldConfig[] { const searchParams = useSearchParams(); - const fields = searchParams.get(SEARCH_PARAM_NAMES.FIELDS); + const fields = searchParams.get(SEARCH_PARAM_NAMES.SEARCH.FIELDS); return useMemo(() => { if (!fields) return DEFAULT_DISPLAY_FIELDS; const fieldConfigs = parseDisplayFieldConfigs(fields); diff --git a/src/features/search/search-bar.tsx b/src/features/search/search-bar.tsx index 8877675..bfecdd3 100644 --- a/src/features/search/search-bar.tsx +++ b/src/features/search/search-bar.tsx @@ -4,7 +4,7 @@ import { forwardRef } from "react"; import { Button } from "@/components/button"; import { SearchIcon } from "@/components/icons/search-icon"; -import { useShinryoukouiSearch } from "@/hooks/use-shinryoukoui-search"; +import { useShinryoukouiSearchByQuery } from "@/hooks/use-shinryoukoui-search"; import { SearchInput } from "./search-input"; @@ -20,7 +20,7 @@ export type SearchBarHandle = { export const SearchBar = forwardRef(function SearchBar( { value, onChange }, ref ) { - const search = useShinryoukouiSearch(); + const search = useShinryoukouiSearchByQuery(); const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); search(event.currentTarget.q.value); diff --git a/src/features/search/search-input.tsx b/src/features/search/search-input.tsx index 6357ee4..246db22 100644 --- a/src/features/search/search-input.tsx +++ b/src/features/search/search-input.tsx @@ -4,6 +4,8 @@ import { forwardRef } from "react"; import { experimental_RichInput as RichInput, RichInputHandle, createRegexRenderer } from "rich-textarea"; import { twMerge } from "tailwind-merge"; +import { SEARCH_PARAM_NAMES } from "@/search-param-names"; + import { shinryokouiMasterFields } from "../shinryoukoui-master-fields/shinryoukoui-master-fields"; const FIELD_NAMES = shinryokouiMasterFields.map((f) => f.name); @@ -33,7 +35,7 @@ export const SearchInput = forwardRef(functio <> ({ key: v, label: `${+v.slice(0, 4)}年${+v.slice(4, 6)}月${+v.slice(6, 8)}日` })); - -export const MASTER_VERSION_SEARCH_PARAM_NAME = "v"; \ No newline at end of file diff --git a/src/hooks/use-shinryoukoui-search.tsx b/src/hooks/use-shinryoukoui-search.tsx index 5ea0315..4339598 100644 --- a/src/hooks/use-shinryoukoui-search.tsx +++ b/src/hooks/use-shinryoukoui-search.tsx @@ -1,23 +1,48 @@ import { useRouter } from "next/navigation"; import { useCallback } from "react"; -import { MASTER_VERSION_SEARCH_PARAM_NAME, SHINRYOUKOUI_MASTER_VERSION_KEYS } from "@/features/shinryoukoui-master-versions/constants"; +import { SEARCH_PARAM_NAMES } from "@/search-param-names"; +export type SearchParamNames = typeof SEARCH_PARAM_NAMES["SEARCH"][keyof typeof SEARCH_PARAM_NAMES["SEARCH"]]; +type Options = { + preserveExtraSearchParams: boolean +} -export function useShinryoukouiSearch() { +export function useShinryoukouiSearch({ + preserveExtraSearchParams, +}: Options) { const { push } = useRouter(); - return useCallback((query: string) => { - const currentSearchParams = new URLSearchParams(location.search); - const masterVersion = currentSearchParams.get(MASTER_VERSION_SEARCH_PARAM_NAME); + return useCallback((params: { + [key in SearchParamNames]?: string | undefined + }) => { + const currentSearchParams = new URLSearchParams(window.location.search); const searchParams = new URLSearchParams(); - if (query) { - searchParams.set("q", query); - } - if (masterVersion && SHINRYOUKOUI_MASTER_VERSION_KEYS.includes(masterVersion)) { - searchParams.set(MASTER_VERSION_SEARCH_PARAM_NAME, masterVersion); - } + + const searchParamNames = Object.values(SEARCH_PARAM_NAMES.SEARCH); + currentSearchParams.forEach((value, key) => { + if (preserveExtraSearchParams || searchParamNames.includes(key as SearchParamNames)) { + searchParams.set(key, value); + } + }); + + Object.entries(params).forEach(([key, value]) => { + if (value) { + searchParams.set(key, value); + } else { + searchParams.delete(key); + } + }); push(`/s?${searchParams.toString()}`); - }, [push]); + }, [preserveExtraSearchParams, push]); +} + +export function useShinryoukouiSearchByQuery() { + const search = useShinryoukouiSearch({preserveExtraSearchParams: false}); + return useCallback((q: string) => { + search({ + q, + }); + }, [search]); } \ No newline at end of file diff --git a/src/search-param-names.ts b/src/search-param-names.ts index 39d0f37..ba908bf 100644 --- a/src/search-param-names.ts +++ b/src/search-param-names.ts @@ -1,3 +1,7 @@ export const SEARCH_PARAM_NAMES = { - FIELDS: "fields", -}; \ No newline at end of file + SEARCH: { + QUERY: "q", + MASTER_VERSION: "v", + FIELDS: "fields", + } +} as const;