frontend/react

[React] React.js 실무 강좌 39. React Query로 다중 페이지 데이터 동기화 — 한 곳에서 바꾸면 전부 반영되게 만들기

mirabo01 2025. 11. 11. 08:54

React Query를 쓰다 보면 정말 자주 부딪히는 문제가 있습니다.
“상세 페이지에서 데이터를 수정했는데, 목록 페이지는 그대로다.”
처음엔 단순히 새로고침으로 해결했지만,
프로젝트 규모가 커질수록 이런 불일치가 UX를 크게 해칩니다.

특히 관리자 페이지나 대시보드처럼
데이터가 여러 컴포넌트에 동시에 뿌려지는 구조에서는
이 문제를 제대로 잡지 않으면 서비스 전체가 불안정해집니다.

이번 글에서는 React Query로
한 곳에서 데이터가 바뀌면 모든 관련 화면이 즉시 반영되는 구조
어떻게 구현했는지 정리해보려 합니다.


페이지마다 상태가 따로 놀던 시절

React Query를 도입하기 전에는
데이터를 컴포넌트별로 useEffect와 useState로 관리했습니다.

useEffect(() => {
  axios.get('/api/users').then(res => setUsers(res.data));
}, []);

목록 페이지와 상세 페이지가 각각 데이터를 불러오니까
상세에서 수정한 내용이 목록에 바로 반영되지 않았습니다.
이걸 해결하려고 props로 상태를 끌어올리거나
Redux로 전역 상태를 공유하는 식으로 우회했죠.

그런데 이런 방식은
데이터가 많아질수록 코드가 복잡해지고
비동기 흐름이 꼬이기 시작했습니다.

React Query를 쓰면서 이 구조가 완전히 바뀌었습니다.


invalidateQueries, 이 한 줄의 힘

React Query의 가장 강력한 기능 중 하나가 invalidateQueries입니다.
이 함수는 특정 queryKey를 가진 데이터를 “오래된 상태”로 표시해서
다시 불러오게 만드는 역할을 합니다.

const queryClient = useQueryClient();
const mutation = useMutation(updateUser, {
  onSuccess: () => {
    queryClient.invalidateQueries(['users']);
  },
});

상세 페이지에서 사용자를 수정하면
React Query는 ['users'] 키로 캐시된 모든 데이터를 자동으로 새로고침합니다.
즉, 목록 페이지로 돌아가도
수정된 데이터가 이미 반영되어 있습니다.

한 줄로 해결되는 이 구조를 처음 써봤을 때,
진짜 깜짝 놀랐습니다.
이전에는 상태 동기화를 위해
몇십 줄씩 작성하던 코드를 지워버릴 수 있었으니까요.


이걸 잘못 써서 한참 삽질했던 시기

invalidateQueries를 처음 쓸 때는
그냥 막연히 “invalidateQueries만 호출하면 되겠지” 하고 썼습니다.
그런데 페이지 전체가 매번 리패치되는 현상이 생겼습니다.

원인은 단순했습니다.
쿼리 키를 너무 포괄적으로 설정했기 때문입니다.

예를 들어
queryKey: ['user']로 등록해둔 쿼리가 여러 개라면,
invalidateQueries(['user'])를 호출할 때
전혀 관련 없는 요청까지 전부 다시 불러오게 됩니다.

그래서 결국 쿼리 키 설계를 세분화했습니다.

  • 전체 목록: ['users']
  • 상세 보기: ['user', userId]
  • 검색 결과: ['users', 'search', keyword]

이렇게 구조를 잡아두면
필요한 데이터만 정확히 리패치되면서
불필요한 네트워크 낭비를 막을 수 있습니다.


실무에서 정말 빛을 발했던 순간

한 프로젝트에서는 사용자 목록, 상세, 통계 페이지가 따로 있었는데
이 세 화면이 전부 같은 데이터 소스를 보고 있었습니다.
이전엔 상세 페이지에서 사용자 정보를 수정하면
목록과 통계가 새로고침 전까지 업데이트되지 않았습니다.

React Query로 바꾼 뒤엔,
상세 페이지에서 mutation 성공 시 invalidateQueries를 호출하면서
세 페이지가 동시에 최신 상태를 반영했습니다.

그때 느꼈던 쾌감이 아직도 기억납니다.
“이제 새로고침 버튼은 없어도 되겠구나.”


데이터 일관성의 기준을 바꿔버린 변화

React Query를 쓰면
“어디서 데이터를 불러오든, 같은 쿼리 키라면 같은 데이터”라는 전제가 생깁니다.
덕분에 데이터 일관성이 자연스럽게 보장됩니다.

예전처럼 전역 상태를 억지로 공유하거나
리렌더링을 유도할 필요가 없습니다.
React Query는 이미 내부적으로 데이터의 생명주기를 관리해주니까요.

결국 중요한 건
**“쿼리 키를 어떻게 설계하느냐”**입니다.
이걸 잘 잡아두면 invalidateQueries만으로
앱 전체의 데이터 흐름을 제어할 수 있습니다.


캐시와 실시간의 경계에서

invalidateQueries는 데이터가 수정된 후의 흐름을 관리하지만,
실시간으로 반영되어야 하는 데이터에는 한계가 있습니다.
예를 들어 채팅이나 대시보드처럼
데이터가 초 단위로 바뀌는 경우엔
WebSocket이나 SSE를 결합해야 합니다.

하지만 대부분의 비즈니스 앱에서는
invalidateQueries로도 충분히 일관성을 유지할 수 있습니다.
그만큼 React Query의 캐시 구조가 안정적입니다.


React Query의 invalidateQueries는 단순한 리패치 기능이 아닙니다.
데이터를 중심으로 생각하는 방식을 만들어주는 도구였습니다.
이걸 이해한 순간,
비동기 로직이 복잡하던 서비스 구조가
“데이터 중심 아키텍처”로 한 단계 성숙해졌습니다.

다음 글에서는
**React Query의 옵티미스틱 업데이트(Optimistic Update)**를 다뤄보겠습니다.
서버 응답을 기다리지 않고 즉시 UI를 업데이트하는 방법인데,
UX를 한 단계 더 매끄럽게 만드는 기술입니다.