Published on

SWR (state-while-revalidate)

Authors
  • avatar
    Name
    Inhwan Cho
    Twitter

SWR

reactQuery와 유사합니다 reactQuery는 기능이 더 많지만 용량도 크기 때문에, 간단한 용도로 사용할때는 SWR이 적합합니다.

page에 접근 -> SWR(fetch data -> 캐싱) -> 다른 페이지(혹은 다른 화면) -> page 접근 -> SWR(API 호출 - 데이터가 바뀐게 있는지 확인 - loading이 없고 캐시에 있는 데이터 먼저 사용) -> 바뀐게 없으면 기존의 data(캐시에 있는) 사용 -> 바뀐게 있으면 data update

즉, 기존의 fetch를 사용하면 page에 접근 했을때 마다 undefined와 data를 받는데, SWR를 사용하면 data변경이 없으면 처음 page에 접근 할 때만 data를 받게됩니다.

그렇기 때문에 reactQuerySWR같은 캐싱을 이용한 방법을 사용해야합니다.

기존의 fetch 방법과 비교

기존방법.tsx
import { useRouter } from "next/router";
import { useEffect, useState } from "react";

export default function Home() {
  const [user, setUser] = useState();
  const router = useRouter();
  useEffect(() => {
    fetch("/api/users/home")
      .then((response) => response.json())
      .then((data) => {
        if (!data.ok) {
          return router.replace("/login");
        } else {
          setUser(data.profile);
        }
      })
      .catch((error) => {
        console.log(error);
      });
  }, [router]);
  return user;
}
  • 단 fetch로 불러온 데이터는 캐싱이 되지 않음.

SWR 사용하기

  • 방법 1. 상위 layout에 SWRConfig를 감싸기(추천)
layout.tsx
<SWRConfig value={{
  fetcher: (url: string) =>
          fetch(url).then((response) => response.json()),
          refreshInterval:2000,
      }}>
  <Component/>
</SWRConfig>
SWR.tsx
import useSWR from "swr";

export default function useUser() {
  const { data, error } = useSWR('/api/users/home')
  // ...
}
  • 방법 2. SWRConfig를 사용하지 않고 하나씩 fetcher를 생성(비추천)
SWR.tsx
import useSWR from "swr";

const fetcher = (url: string)=> fetch(url).then((response)=>response.json())

export default function useUser() {
  const { data, error } = useSWR('/api/users/home', fetcher)
  const router = useRouter();
  useEffect(()=>{
    if(data && !data.ok){
      router.replace('/login')
    }
  },[data, router])

  return { user: data?.profile, isLoading: !data && !error };
}

useSWR(key, fetcher)에 인자가 2개가 들어가는데, key에 url이 들어가는데 이 url이 캐싱이 될 때의 key(super_cache)값으로 들어갑니다.

참고로 router.push()는 페이지를 이동(전페이지에 대한 뒤로가기 기록이 있음) 그래서 뒤로가기를 누르면 전페이지 이동-> push()한 페이지로 이동됨 router.replace()는 페이지를 이동시키고 기록을 남기지 않음 즉 (뒤로가기를 누르면 그 전페이지가 아닌 전전페이지로 이동하게 만듦)

mutate

먼저 useSWR로 data와 mutate를 받는데, 이 mutate는 data와 동일한 형태이며, mutate를 변경 시 data를 변경합니다. 이를 이용하여 백엔드로 데이터를 넘기고 받아오기 전에 UI를 front에서 미리 바꿀 수 있게 많이 사용합니다. 특히, 현재 보고 있는 페이지 내의 데이터를 변경하는것을 unbound mutate, 현재 보고있는 페이지 외의 데이터를 변경하는 것을 bound mutate라 합니다.

  1. unbound mutate
SWR.tsx
import useSWR from "swr";

export default function likeTest() {
  const apiAddress = 'https://sadfsfadsf.com'
  const { data, mutate } = useSWR(apiAddress);

  // mutate를 이용하여 전체 기존의 데이터를 넣고, 바꾸고 싶은 데이터를 첫번째 인즈에 넣고,
  // 두번째 인자에는 true는 재검증(refetch)하여 백엔드의 데이터와 일치하는지 확인.(default)
  // false는 재검증을 하지 않아 바뀐 값 그대로 front 에서만 유지합니다.
  const handleLike = () => {
    if (!data) return; //여러번 클릭 시 1회만 실행
    mutate({
      ...data,
      isLiked: !data.isLiked
    }, false);
  };

  // mutate로 apiAddress를 다시 할 경우 재검증을 하게됩니다.
  const handleRefetch = () => {
    if (!data) return; //여러번 클릭 시 1회만 실행
    mutate(apiAddress)
  }
  return (
      <div className="grid grid-cols-2 gap-5 mx-6 my-2">
        <div onClick={handleRefetch} className={`bg-white p-4 rounded-lg flex justify-center cursor-pointer`}>Let&apos;s refetch!</div>
        <div onClick={handleLike} className={`${data.isLiked ? 'bg-red-300' : 'bg-blue-400'} p-4 rounded-lg flex justify-center cursor-pointer`}>{data.isLiked ? 'Unlike' : 'Like'}</div>
      </div>
  )
}
  1. bound mutate

주로 로그아웃 같은 전역 변수를 수정할때 사용합니다.

import { useSWRConfig } from "swr"
 
function App() {
  const { mutate } = useSWRConfig()
  mutate(key, data, options)
}