Next.js

[Next.js] URL query string 관리하기

dev_seon 2023. 4. 10. 23:57

Next.js 13의 useSearchParams hook은 현재 URL의 query string을 읽을 수 있게 해주는 hook으로, read-only 버전의 URLSearchParams 인터페이스를 return합니다. useSearchParams hook의 사용 방법은 아래와 같습니다.

 

// url: /?note=4%7C10
import { useSearchParams } from 'next/navigation';

const searchParams = useSearchParams();

console.log(searchParams.get('note')); // 4|10
console.log(searchParams.has('note')); // true
console.log(searchParams.has('brand')); // false

 

그러나 Next.js의 useSearchParams hook은 read-only입니다. 때문에 공식 문서에서는 query string을 추가하고, 업데이트하고, 삭제하기 위해서는 URLSearchParams 인터페이스를 활용하는 방식을 예시로 제시하고 있었습니다. 

 

// Next.js 공식문서의 Updating searchParams 예시
export default function ExampleClientComponent() {
  const router = useRouter()
  const pathname = usePathname()
  const searchParams = useSearchParams()!
 
  // Get a new searchParams string by merging the current
  // searchParams with a provided key/value pair
  const createQueryString = useCallback(
    (name: string, value: string) => {
      const params = new URLSearchParams(searchParams)
      params.set(name, value)
 
      return params.toString()
    },
    [searchParams]
  )
 
  return (
    <>
      <p>Sort By</p>
 
      {/* using useRouter */}
      <button
        onClick={() => {
          // <pathname>?sort=asc
          router.push(pathname + '?' + createQueryString('sort', 'asc'))
        }}
      >
        ASC
      </button>
 
      {/* using <Link> */}
      <Link
        href={
          // <pathname>?sort=desc
          pathname + '?' + createQueryString('sort', 'desc')
        }
      >
        DESC
      </Link>
    </>
  )
}

 

이러한 예시와 검색 결과를 바탕으로 아래와 같은 cutom hook을 만들었습니다.

 

 

import { usePathname, useRouter, useSearchParams } from 'next/navigation';

function useCustomSearchParams<T extends Partial<{ [key: string]: string[] | undefined }>>() {
  const searchParams = useSearchParams();
  const pathname = usePathname();
  const router = useRouter();
  const urlSearchParams = searchParams ? new URLSearchParams(searchParams) : null;

  function setSearchParams(params: Partial<T>) {
    Object.entries(params).forEach(([key, value]) => {
      if (!value.length) urlSearchParams?.delete(key);
      else urlSearchParams?.set(key, String(value.join('|')));
    });

    const paramsToString = urlSearchParams?.toString();
    const query = paramsToString ? `?${paramsToString}` : '';
    
    router.push(`${pathname}${query}`);
  }

  return { searchParams, setSearchParams };
}

export default useCustomSearchParams;

 

위의 hook은 query string을 활용하여 필터 기능을 구현하기 위해 만들었는데, 관리해야 하는 필터의 항목이 두 개 였기 때문에 setSearchParams 함수에 객체 형태의 parameter를 전달할 수 있도록 구현했습니다.

이 때 setSearchParams 함수는 parameter로 전달 받은 객체를 순회하며 key에 해당하는 value의 length가 1 이상일 경우 urlSearchParams에 key, value를 set 하고, value의 length가 0일 경우 urlSearchParams에서 key를 삭제합니다. 

이후 urlSearchParams를 string 형태로 변환하고, 이를 pathname과 함께 router에 push해 주면 "/?brand=1%7C2&note=4" 의 형태로 URL을 관리할 수 있습니다.

 

참고 자료

- https://nextjs.org/docs/app/api-reference/functions/use-search-params

- https://developer.mozilla.org/ko/docs/Web/API/URLSearchParams

- https://github.com/vercel/next.js/discussions/47583