지난 이야기
SWR의 캐시 이슈 2탄이다.
1탄에서는 SWR의 최신 데이터가 반영되지 않는 버그가 존재하였고,
useEffect를 통해 최신 데이터를 직접 반영해주었다.
useEffect(() => { setIsLikedFeed(isLiked); setFeedLikeCount(likeCount); }, [isLiked, likeCount]);
그러나 이 방법은 임시방편이며, 불완전하다는 느낌을 지울 수 없다.
특히 페이지 마운트 이후, 변경사항을 직접 반영해주는 방식이므로
이전 데이터가 잠깐이지만 보이는 현상이 발생한다.
아래 영상을 참고해주시기 바란다.
좋아요가 깜빡거리며 바뀌는 현상이 관찰된다.
어떻게보면 사소하다고 할 순 있지만, UX 측면에서 좋다고 할 수 없는 문제이다.
무엇보다, SWR의 캐시 데이터를 제대로 활용하지 못하고 있다는 생각이 든다.
SWR의 mutate란?

SWR의 mutate는 캐시된 데이터를 mutate(변화)할 수 있는 함수이다.
useSWRConfig로부터 mutate 함수를 얻을 수 있으며,
mutate(key)를 호출하여 동일한 키를 사용하는
다른 SWR hook에게 전역적으로 갱신 명령을 내릴 수 있다.
예를 들어, ’/reviews/123’이라는 키를 갖는 SWR hook이 여러 개 있다면
mutate(’/reviews/123’)를 사용함으로써
해당 키를 가진 hook들의 캐시 데이터를 모두 갱신할 수 있는 것이다.
mutate(key, data, options)
mutate의 첫 번째 인자는 key값이다.
두 번째 인자로 갱신할 데이터가 있다면 넣어준다.
세 번째 인자는 옵션으로, revalidate 및 optimisticData 등의 옵션이 존재한다.
자세한 것은 SWR의 공식문서를 참고해주기 바란다.
프로젝트 적용
문제가 발생한 CommunityPage, ReviewFeed, ReviewDetail에 적용할 것이다.
CommunityPage에 여러 개의 ReviewFeed가 있는 형태이며,
각각의 ReviewFeed를 클릭하면 ReviewDetail로 이동하는 구조다.
이 세 개의 컴포넌트는 서로 연결되어 있어서
캐시 데이터를 함께 관리해야 하는 다소의 어려움이 존재하였다.
CommunityPage

ReviewFeed

우선 useSWRConfig 함수를 통해 mutate를 가져온다.
const { mutate } = useSWRConfig();
좋아요가 클릭되면 handleLikeClick 함수가 호출된다.
여기에 mutate를 추가해보자.
const handleLikeClick = async (reviewId: number) => { // 낙관적 업데이트 setIsLikedFeed(!isLikeFeed); setFeedLikeCount(isLikeFeed ? feedLikeCount - 1 : feedLikeCount + 1); const { data } = await reviewAPI.likeToggle(reviewId); const { likeCount, isLiked } = data.data; setIsLikedFeed(isLiked); setFeedLikeCount(likeCount); // mutate 추가 mutate(`api/v1/reviews/${reviewId}`, undefined, { revalidate: true }); };
api/v1/reviews/${reviewId}
라는 key를 가진 SWR hook에게 캐시 유효성 검증 명령을 내린다.
이것은 ReviewDetail에서 사용하는 key이다.
즉, ReviewFeed의 데이터가 갱신되었으므로
ReviewDetail에도 캐시 데이터의 갱신 명령을 내리는 것이라고 할 수 있다.
ReviewDetail

ReviewFeed를 클릭하면 ReviewDetail로 이동한다.
여기에도 mutate를 적용해보자.
역시 useSWRConfig 함수를 통해 mutate를 가져온다.
const { mutate } = useSWRConfig();
ReviewDetail에서도 좋아요를 클릭하면
handleLikeClick이라는 함수가 호출된다.
const handleLikeClick = async (reviewId: number) => { setIsLikedFeed(!isLikeDetail); setDetailLikeCount(isLikeDetail ? detailLikeCount - 1 : detailLikeCount + 1); const { data } = await reviewAPI.likeToggle(reviewId); const { likeCount, isLiked } = data.data; setIsLikedFeed(isLiked); setDetailLikeCount(likeCount); // 추가한 부분 mutate( `api/v1/reviews/${reviewId}`, { ...reviewDetail, isLiked, likeCount, }, { revalidate: false }, ); };
이번에도
api/v1/reviews/${reviewId}
라는 key를 가진 SWR hook에게 캐시 갱신 명령을 내린다.
그러나 이번에는 클라이언트에서 갱신할 데이터를 가지고 있는 상황이다.
reviewDetail이 그렇고, isLiked와 likeCount도 가지고 있다.
따라서 굳이 서버에서 유효성 검증을 할 필요는 없다.
두 번째 인자의 데이터로 캐시를 갱신하라고 넣어주고,
세 번째 인자에는 revalidate: false를 통해 유효성 검증을 하지 않도록 한다.
사전에 말해두고 싶은 것은,
이 페이지 및 컴포넌트들은 팀 동료의 작업물이란 사실이다.
그렇기 때문에 왠만하면 코드를 너무 많이 수정하고 싶지는 않았다.
필요한 곳에만 mutate를 적재적소에 사용하여 문제를 해결하고 싶었다.
그래서 muate를
코드의 로직을 많이 수정하지 않고 mutate를 적용하도록 하겠다.