공부블로그

Link로 이동했는데 이전 필터가 남아있다? - 리렌더와 리마운트의 차이, 그리고 React Query 캐시 전략 본문

공부하기/Trouble Shooting

Link로 이동했는데 이전 필터가 남아있다? - 리렌더와 리마운트의 차이, 그리고 React Query 캐시 전략

떠어영 2025. 8. 1. 13:57

Next.js 기반 어드민 페이지를 개발하면서, 정산 실패한 경우 특정 리포트의 상세 페이지로 리다이렉트해야 하는 요구사항이 생겼다. 이건 searchParams로 파라미터 넘겨서 잘 처리했는데...

문제는 여기서부터였다.

❓문제 상황

  • 정산 상세 페이지를 보고 있다가,
  • 메뉴에서 동일한 상세 페이지 경로를 <Link>로 클릭해서 이동하면,
  • 전체 상세가 아닌, 이전에 필터링된 결과가 그대로 남아 있는 것!

🧠 내가 처음에 했던 추측

“React Query가 쿼리 키를 업데이트하지 않아서 그런가?” → ❌ 쿼리 키는 잘 바뀌고 있음
“그럼 캐시가 남아있나?” → ❌ 서버 컴포넌트에서 prefetch도 잘 되고 있음

 

그런데도 여전히 이전 필터값으로 조회된 데이터가 유지되고 있었다.


🔍 디버깅 결과

  • <Link> 클릭 → 서버 컴포넌트 재실행됨
  • searchParams를 기반으로 prefetch 수행됨 (settleSummaryStatus 없음 = 필터 없음)
  • 이 값들을 props로 클라이언트 컴포넌트(SettlementDetail)에 넘김

그런데도...

  • SettlementDetail 내부 상태(queryParams)는 이전에 필터된 상태(settleSummaryStatus: FAILED) 유지
  • 즉, React는 컴포넌트를 리렌더만 하고 리마운트하지 않음
  • 그래서 useState()로 초기화한 queryParams는 한 번 세팅된 이후로 바뀌지 않음

✅ 문제 핵심 요약

- props는 바뀜 → 리렌더 발생
- 내부 useState는 그대로 → queryParams는 이전 필터 상태
- useGetSettlementDetail(queryParams) → 이전 필터 데이터 요청

🧩 이쯤에서 다시 보는: 리렌더 vs 리마운트

  리렌더 (rerender) 리마운트 (remount)
언제 발생? props/state 변경 key 변경되거나 컴포넌트가 트리에서 제거됐다가 다시 삽입될 때
내부 상태 (useState) ✅ 유지됨 ❌ 초기화됨
useEffect(() => ..., []) ❌ 실행 안됨 ✅ 다시 실행됨
React Query 훅 쿼리 키가 같으면 캐시 그대로 사용 훅은 재실행되지만, 쿼리 키가 같으면 캐시 사용됨 (옵션에 따라 서버 재요청 가능)

🔍 진짜 원인은 이거였다

내가 실수한 건,
"props가 바뀌니까 리렌더 되면 초기화될 거라고 착각했다"는 점!

 

하지만 리렌더는 state를 유지하고, 리마운트만이 상태를 초기화한다.
그리고 React Query는 쿼리 키가 바뀌지 않으면 이전 캐시를 사용한다.


🛠 해결 방법

1️⃣ 컴포넌트에 key를 줘서 강제로 remount

<SettlementDetail key={query} filter={filter} ... />

 

이렇게 하면 컴포넌트가 완전히 새로 마운트되고, 내부 상태도 다시 초기화된다.


2️⃣ 상태를 props 기반으로 동기화 (useEffect)

useEffect(() => {
  setQueryParams({
    page: 0,
    size: 20,
    searchParams: {
      fromSettleMonth: maxYM,
      toSettleMonth: maxYM,
    },
    filters: filter ? { settleSummaryStatus: [filter] } : {},
  });
}, [filter, maxYM]);

3️⃣ 상태 없이 useMemo로 항상 최신 값 계산

const queryParams = useMemo(() => ({
  page: 0,
  size: 20,
  searchParams: {
    fromSettleMonth: maxYM,
    toSettleMonth: maxYM,
  },
  filters: filter ? { settleSummaryStatus: [filter] } : {},
}), [filter, maxYM]);

🚀 <Link>는 서버 컴포넌트를 거칠까?

이동 방식 서버 컴포넌트 실행 여부

브라우저 새로고침 / 직접 접근 ✅ 항상 실행
<Link href="..."> ✅ 서버 컴포넌트 실행됨 (App Router 기준)
router.push() ❌ 클라이언트 라우팅만 발생 (서버 안 거침)

 

→ 나는 <Link>로 이동했기 때문에 서버 컴포넌트의 로그가 다시 찍혔고, 서버에서 props도 새로 내려왔던 것.


✅ 결론

  • 리렌더와 리마운트의 차이,
  • React Query의 캐시 전략,
  • 초기 상태를 props에 의존할 때의 함정

이 세 가지가 모여서 아주 흔하지만 까다로운 버그를 만들었다.

초기 props 기반으로 useState() 세팅할 땐 반드시 리마운트 조건까지 고려하자.!!


🧑‍💻 나는 어떻게 해결했나?

나는 위에서 말한 1번 방식, key를 통해 컴포넌트를 강제로 remount하는 방법을 선택했다.

<SettlementDetail key={query} ... />
 
이렇게 하면 props가 바뀔 때마다 컴포넌트가 완전히 새로 마운트되면서 내부의 useState들도 초기화된다.

그럼에도 불구하고 React Query는 동일한 쿼리 키를 기준으로 캐시된 데이터를 그대로 사용할 수 있기 때문에, 성능상 손해도 거의 없고, UX도 자연스럽다.

즉,

“컴포넌트를 새로 마운트해서 상태 초기화는 하고, 쿼리 결과는 캐시된 걸 재사용”

 

이라는 구조를 만들 수 있었고, 내 케이스엔 이게 가장 깔끔하고 직관적인 해결책이었다.