상단 고정(Pin) 버튼 클릭 시,
UI가 바로 바뀌지 않아 느리게 느껴지는 문제가 있었습니다.
서버 응답을 기다리기 때문에 사용자는 반응이 늦다고 느낍니다.
인스타그램 하트 버튼처럼 “클릭 즉시 반응”하는 인터랙션이 필요했습니다.
window.addEventListener("scroll", updatePosition);
React Query는 서버 데이터 캐시를 조작하는 두 가지 방법을 제공합니다.
// 1️⃣ invalidateQueries - 캐시 무효화 후 refetch
queryClient.invalidateQueries({ queryKey: ["user"] });
// → 기존 캐시 삭제 → 서버에 다시 요청 → 새 데이터로 교체
// 2️⃣ setQueryData - 캐시 직접 수정
queryClient.setQueryData(["user"], { name: "John" });
// → 서버 요청 없이 캐시 값 직접 변경
즉,
invalidateQueries: 서버에서 새 데이터를 받아오기 때문에 느리지만 안전setQueryData: 캐시를 즉시 바꿔서 빠르지만, 서버와 불일치 가능성 있음✅ 토글 버튼처럼 true/false를 즉시 바꿀 때는
이미 새 상태를 알고 있기 때문에 setQueryData 가 적합합니다.
Optimistic Update(낙관적 업데이트) 는
서버 요청이 성공할 거라고 “낙관적으로” 가정하고
UI를 먼저 업데이트하는 패턴입니다.
const togglePin = (linkId: string, currentPinState: boolean) => {
const next = !currentPinState;
// ✅ 즉시 UI 업데이트 (서버 응답 전)
queryClient.setQueryData(["linkDetail", linkId, boxId], (old) =>
old ? { ...old, data: { ...old.data, isPin: next } } : old
);
// ⚙️ 백그라운드에서 서버 요청
mutate({ linkId, boxId, currentPinState });
};
👉 클릭 → 즉시 UI 변경 → 서버 요청 (백그라운드)
👉 사용자가 느끼는 지연 시간: 0ms
Pin 버튼은 Link List와 Link Detail 두 곳에서 사용됩니다.
LinkDetail에서만 캐시를 바꾸면리스트 캐시는 아래처럼 InfiniteData 형태로 저장되어 있습니다.
{
pages: [
{ items: [
{ id: "1", type: "folder" },
{ id: "2", type: "link", isPin: false }, // 이 pin을 눌렀을 때 상태를 바꿔야 함
{ id: "3", type: "link", isPin: true },
]},
{ items: [
{ id: "21", type: "link", isPin: false },
{ id: "22", type: "folder" },
]},
// ... 수십 개의 페이지
]
}
➡️ 하지만 어느 페이지에 해당 링크가 있는지 모름
따라서 모든 페이지를 순회하며 해당 item을 찾아야 함.
React Query v5부터는 여러 캐시를 한 번에 갱신할 수 있는
setQueriesData()가 도입되었습니다.
queryClient.setQueriesData<InfiniteData<ListResponse>>(
{
queryKey: ["list", boxId], // folderId 생략
exact: false, // 부분 일치 허용 (fuzzy matching)
},
(old) =>
old
? {
...old,
pages: old.pages.map((page) => ({
...page,
items: page.items.map((item) =>
item.type === "link" && item.id === linkId
? { ...item, isPin: next } // ✅ 핀 상태만 변경
: item
),
})),
}
: old
);
exact: false의 의미exact: false는 query key의 앞부분만 일치하면 모두 매칭하는 기능입니다.
// 캐시 상태
["user", "1"]
["user", "1", "posts"]
["user", "1", "posts", "comments"]
["user", "2"]
| 설정 | 매칭되는 키 | 설명 |
|---|---|---|
exact: true | ["user", "1"] | 길이와 요소가 완전히 일치해야 함 |
exact: false | ["user", "1"], ["user", "1", "posts"], ["user", "1", "posts", "comments"] | 앞부분만 일치하면 모두 OK |
즉,
"list"관련 캐시가 여러 개 있더라도,
["list", boxId]로 시작하는 모든 쿼리 캐시를 한 번에 갱신할 수 있습니다.
| 항목 | 설명 |
|---|---|
invalidateQueries | 캐시 무효화 후 서버에서 다시 가져옴 (느리지만 안전) |
setQueryData | 캐시를 즉시 변경 (빠르지만 수동 관리 필요) |
setQueriesData | 여러 캐시를 한 번에 갱신 가능 |
exact: false | queryKey 앞부분만 일치해도 매칭 (부분 갱신) |
| Optimistic Update | 서버 성공을 가정하고 UI 먼저 변경 |
setQueryData / setQueriesData 활용exact: false 필수