Frontend/Today I Learned

[Next.js] 버튼 클릭 시 query param 추가하기

joycie416 2024. 10. 24. 22:11

쇼핑몰에서 하는 것처럼 검색어나 필터를 입히면 query param이 추가되도록 하고 싶었다. 나중에 query param을 통해 정보를 불러올 것이므로 이 방법을 쓰는 것이 적합할 것 같았다.

 

그 전에 next의 `usePathname`과 `useSearchParams`에서 필요한 부분만 살펴보고 가려고 한다.

 

`usePathname()`

먼저 쿼리 스트링이 입력된 경우 pathname이 어떻게 출력될지 궁금했다.

`http://localhost:3000/mypage?myKey=hihi` 이와 같은 경로에서 `usePathname`을 통해서 경로를 가져오면 어떤 값을 줄까? 쿼리 스트링은 모두 제거한 path만 깔끔하게 반환해준다.

'use client'

import { usePathname } from "next/navigation";

const Page = () => {
  const pathname = usePathname();
  console.log(pathname); // '/mypage'

  return (
  ...
  )
};

 

`useSearchParams()`

이제 쿼리 스트링이 입력된 경우 해당 내용들을 key-value 쌍으로 가져와보자.

자주 사용할만한 메서드들을 next 공식 문서에서 소개하고 있다. 내 생각에는 `.has()`과 `.get()`을 많이 쓸 것 같다. `.size`라는 속성도 있는데, 필요한 쿼리 파라미터 개수가 정해져 있다면 size를 통해 빠르게 확인할 수 있을 것 같다.

 

메서드 이름에서 알 수 있듯이, `.has('myKey')`라고 하면 myKey가 쿼리 파라미터로 입력되었는지 확인할 수 있도록 boolean을 반환해준다. `.get('myKey')`라고 하면 myKey가 어떤 값과 대응을 이루는지 알려준다. 이때 myKey가 쿼리 파라미터로 입력되지 않았으면 `null`을 반환하고 다른 경우에는 대응된 값을 반환한다. `?myKey=`과 같은 경우에는 빈 문자열 `''`을 반환하고, 반환 값들은 모두 문자열이다.

'use client'

import { useSearchParams } from "next/navigation";

const Page = () => {
  const searchParams = useSearchParams();
  console.log(searchParams.size); // 1
  console.log(searchParams.has('myKey')); // true
  console.log(searchParams.get('myKey')); // 'hihi'
  console.log(searchParams.get('keykey')); // null

  return (
  ...
  )
};

 

`useRouter()`

이제 검색 조건을 쿼리 파라미터로 넣어보자.

먼저 `.push()` 메서드를 사용하면 `/mypage`일 때 새로운 파라미터들이 잘 추가된다. 그런데 이미 기존 검색 조건을 바꿔서 입력하면 새로 추가된 조건은 잘 추가되지만, 변경된 내용은 반영되지 않았다. 즉 `myKey=123`이 있는데 `myKey=321`로 바꿔서 추가하기위해 `.push('${pathname}?myKey=321')`을 하게 되면 여전히 `myKey=123`으로 남아있었다.

 

이를 해결하기 위해 `.replace()` 메서드를 사용했다. 이름에서처럼 기존에 입력된 것이 있으면 대체해서 쿼리 파라미터를 입력해준다. `${pathname}?myKey=123`을 `.replace(' ${pathname}?myKey=321')로 바꿔주면 `myKey=321`로 변경된다.

 

윗 내용은 dev로 실행했을 때 실행이 느려서 발생했던 오류 같다. `.replace()`를 쓰면 현재 route가 해당 route로 대체돼서 뒤로가기를 누르면 그 전전route로 변경된다. 검색 내용과 별개로 검색 페이지만 유지하고 뒤로가기를 누르면 검색 페이지 밖으로 가고 싶으면 `.replace()`를 사용하면 된다.

 

이렇게 `useRouter()`를 통해 넣어준 쿼리 파라미터를 받아오려면 `useSearchParams()`를 사용하면 된다.

 

 

따라서 위 과정을 적용해서 대략적인 코드를 작성해보면 아래와 같다.

"use client";

import { usePathname, useRouter } from "next/navigation";

const SearchForm = (...) => {
  const router = useRouter();
  const pathname = usePathname();
  
  ...

  const setQueryParams = (params: {[key: string] : string}) => {
    let newParams = `?brtcCd=${params.brtcCd}&sggCd=${params.sggCd}`;

    if (params.addr) {
      newParams += `&addr=${params.addr}`
    }
    if (params.org) {
      newParams += `&org=${params.org}`
    }
    if (params.disease) {
      newParams += `&disease=${params.disease}`
    }
  
    router.replace(`${pathname}${newParams}`);
    return ;
  }

  return (
    <div className="w-full flex flex-col ">
      <form className="hospital-search">
        <Select>
          ...
        </Select>

        <Select>
          ...
        </Select>

        <Input
          placeholder="도로명/동 주소"
          value={addr}
          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
            // 입력값 전후 공백 제거
            setAddr(e.target.value.trim());
          }}
        />
        <Input
          placeholder="병원명"
          value={org}
          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
            // 입력값 전후 공백 제거
            setOrg(e.target.value.trim());
          }}
        />

        <Button
          onClick={(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
            // 입력값 유지하기 위해 preventDefault()
            e.preventDefault();
            const params = { brtcCd: brtc[0], sggCd: sgg[0], addr, org, disease: '' };
            setQueryParams(params)
          }}
        >
          검색
        </Button>
      </form>
      {children}
    </div>
  );
};

export default SearchForm;