본문 바로가기

개발 공부/React

useSearchParams hook 만들기

  • 현재 URL의 쿼리 파라미터를 읽기 쉽게 객체로 변환
  • 특정 쿼리를 업데이트하면서 URL을 갱신 (router.push or router.replace)
  • 모든 쿼리를 초기화 (resetSearchParams)

코드

import { useSearchParams as useNextSearchParams, usePathname, useRouter } from 'next/navigation';
import { useCallback } from 'react';

interface UpdateSearchParamsOption {
  replace?: boolean;
}

export const useSearchParams = () => {
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useNextSearchParams();

  const setSearchParams = useCallback(
    (params: Record<string, string | string[]>, options: UpdateSearchParamsOption) => {
      const search = new URLSearchParams(searchParams);

      for (const [key, value] of Object.entries(params)) {
        search.set(key, Array.isArray(value) ? value.join(',') : value);
      }

      for (const key of Array.from(search.keys())) {
        if (search.get(key) === 'undefined' || search.get(key) === null || search.get(key) === '') {
          search.delete(key);
        }
      }

      const url = `${pathname}?${search.toString()}`;

      if (options.replace) {
        router.replace(url);
      } else {
        router.push(url);
      }
    },
    [router, pathname, searchParams]
  );

  const resetSearchParams = useCallback(() => {
    router.push(pathname);
  }, [router, pathname]);

  return { searchParams: Object.fromEntries(searchParams), setSearchParams, resetSearchParams } as const;
};

 

 

useNextSearchParams() — 쿼리 읽기 전용 훅

Next.js의 useSearchParams()는 현재 URL의 쿼리스트링을 ReadonlyURLSearchParams 형태로 반환합니다.

const searchParams = useNextSearchParams();
console.log(searchParams.get('page')); // "1"
  • 하지만 읽기 전용이라 set()이나 delete() 같은 메서드는 사용할 수 없습니다.
  • 따라서 수정 가능한 복사본을 만들어야 합니다 

new URLSearchParams(searchParams) — 복제해서 수정 가능한 버전 만들기

const search = new URLSearchParams(searchParams);
  • 이 코드는 현재 쿼리 상태를 그대로 복사하여 편집 가능한 새로운 객체를 만듭니다.
  • 이제 아래처럼 자유롭게 수정할 수 있습니다.
search.set('page', '2');
search.delete('keyword');

 

 


 

쿼리 업데이트 로직 (setSearchParams)

for (const [key, value] of Object.entries(params)) {
  search.set(key, Array.isArray(value) ? value.join(',') : value);
}
  • params는 { key: value } 형태의 객체입니다.
  • 값이 배열이면 join(',')으로 묶어서 "a,b,c" 형태로 저장합니다.
  • 기존에 같은 key가 있으면 덮어씁니다.

예시:

setSearchParams({ page: '1', tags: ['ui', 'react'] })
// 결과 → ?page=1&tags=ui,react

 

불필요한 값 정리

for (const key of Array.from(search.keys())) {
  if (search.get(key) === 'undefined' || search.get(key) === null || search.get(key) === '') {
    search.delete(key);
  }
}
  • 빈 문자열(''), 'undefined', null 값은 URL에 남겨둘 필요가 없으므로 자동으로 삭제합니다.
  • 결과적으로 ?q= 같은 쓸모없는 쿼리가 깔끔하게 제거됩니다.

 

URL 문자열 조합

const url = `${pathname}?${search.toString()}`;
  • pathname은 현재 경로(/works 등)
  • search.toString()은 내부 쿼리 데이터를 직렬화하여 'page=1&genre=romance' 형태의 문자열을 반환합니다.

즉, 최종적으로 /works?page=1&genre=romance&keyword=love 같은 URL이 만들어집니다.

 


push vs replace

if (options.replace) {
  router.replace(url);
} else {
  router.push(url);
}
  • router.push() : 브라우저 히스토리에 추가 (뒤로 가기 가능)
  • router.replace() : 현재 기록을 대체 (뒤로 가도 이전 쿼리로 돌아가지 않음)

검색어 입력처럼 기록을 남길 필요가 없는 경우엔 replace,
페이지 이동처럼 뒤로 가기 기능이 필요한 경우엔 push를 사용하는 게 일반적입니다.

 

 

쿼리 초기화 (resetSearchParams)

const resetSearchParams = useCallback(() => {
  router.push(pathname);
}, [router, pathname]);
  • 모든 쿼리를 삭제하고 현재 경로만 남깁니다.
  • 즉, /works?page=1&genre=romance → /works

 


 

객체 형태로 반환하기

return { searchParams: Object.fromEntries(searchParams), setSearchParams, resetSearchParams } as const;

Object.fromEntries(searchParams)

URLSearchParams는 [key, value] 쌍의 iterable입니다.
이를 일반 객체로 변환하면, 코드에서 훨씬 쉽게 접근할 수 있습니다.

Object.fromEntries(searchParams);
// { page: '1', genre: 'romance', keyword: 'love' }

 

이제 다음처럼 접근 가능 

searchParams.page // "1"
searchParams.genre // "romance"

 

 


 

사용 예시

'use client';
import { useSearchParams } from '@/hooks/useSearchParams';

export default function WorkList() {
  const { searchParams, setSearchParams, resetSearchParams } = useSearchParams();

  const onChangePage = (page: number) => {
    setSearchParams({ page: String(page) }, { replace: false });
  };

  const onSearch = (keyword: string) => {
    setSearchParams({ keyword }, { replace: true });
  };

  return (
    <>
      <div>현재 페이지: {searchParams.page ?? '1'}</div>
      <button onClick={() => onChangePage(2)}>2페이지로 이동</button>
      <button onClick={() => onSearch('love')}>검색</button>
      <button onClick={resetSearchParams}>초기화</button>
    </>
  );
}

 

URL 동작 예시

/works?page=2
/works?keyword=love
/works

'개발 공부 > React' 카테고리의 다른 글

Next.js Route Handler  (0) 2025.11.22
Clerk 인증 정보와 Prisma User 테이블 동기화(sync)  (0) 2025.11.06
custom modal 만들기  (0) 2025.10.21
React App Router, Page Route  (0) 2025.10.19
Server Components, Client Components  (0) 2025.09.03