하나의 커스텀 훅으로 여러 페이지에서 쿼리스트링을 통한 데이터 요청을 구현하였다.
해당 커스텀 훅은 useSearchParams 를 사용하여 get, set 메서드를 반환한다. 아래는 최초로 작성했던 코드이다.
import { useSearchParams } from "next/navigation";
import { useRouter } from "next/navigation";
import { useCallback } from "react";
/**
* @example
* const value = get(<keyA>);
* set({ <keyA>: <valueA>, <keyB>: <valueB> });
* set({ <keyA>: null });
* set({ <keyA>: null, <keyB>: <valueB> });
*/
export function useQueryParams() {
const searchParams = useSearchParams();
const router = useRouter();
const get = (key: string) => searchParams.get(key);
const set = useCallback(
(params: Record<string, string | null | undefined>) => {
const newParams = new URLSearchParams(searchParams.toString());
Object.entries(params).forEach(([key, value]) => {
if (value === null || value === undefined) {
newParams.delete(key);
} else {
newParams.set(key, value);
}
});
router.replace(`?${newParams.toString()}`);
},
[searchParams, router],
);
return { get, set };
}
그러나 커스텀 훅에서 router.replace 를 사용하는 경우 렌더링 속도가 심각하게 저하되는 문제가 발생했다.
이를 해결하기 위해 Next.js 공식문서를 확인하고 문제가 없다는 판단 하에 window.history.replaceState 로 변경하였다.
const router = useRouter();
// ...
router.replace(`?${newParams.toString()}`);
// 아래와 같이 변경
window.history.replaceState(null, "", `?${newParams.toString()}`);
그러나 코드를 바꾸고 나서 2가지 이슈가 발생하였다.
get (useSearchParams ),
팀원 B는 prefetch 를 위해 서버 컴포넌트에서 searchParams prop 을 사용했다.
팀원 A의 페이지에서는 문제가 없었지만 팀원 B의 페이지에서 브라우저 주소만 변경되고 리렌더링되지 않았다.router.replace + 메모이제이션 의존성 + searchParams.toString() 사용router.replace → window.history.replaceState로 변경set 의 의존성에서 router 제거 → []로 변경searchParams.toString() → window.location.search로 변경최종 코드는 아래와 같다.
import { useSearchParams } from "next/navigation";
import { useCallback } from "react";
/**
* @example
* const value = get(<keyA>);
* set({ <keyA>: <valueA>, <keyB>: <valueB> });
* set({ <keyA>: null });
* set({ <keyA>: null, <keyB>: <valueB> });
*/
export function useQueryParams() {
const searchParams = useSearchParams();
const get = (key: string) => searchParams.get(key);
const set = useCallback((params: Record<string, string | null | undefined>) => {
const newParams = new URLSearchParams(window.location.search);
Object.entries(params).forEach(([key, value]) => {
if (value === null || value === undefined) {
newParams.delete(key);
} else {
newParams.set(key, value);
}
});
window.history.replaceState(null, "", `?${newParams.toString()}`);
}, []);
return { get, set };
}
searchParams 는 prefetch 할 때만 사용하도록 기존 부모→자식 컴포넌트 전달을 제거하고,
useSearchParams 를 자식에서 사용하도록 구조를 변경하였다.