# SWR > swr (stale-while-revalidate) - 원격 데이터를 가져오기 위한 React Hooks 라이브러리 - SWR은 캐시 데이터를 반환 한 다음 가져오기 요청을 전송하고 최신 데이터를 다시 제공 - revalidateOnFocus, revalidateOnReconnect, refershInterval 등의 기능을 지원하여 프론트와, DB 데이터 간의 차이를 없애주는 다양 한 옵션들이 제공된다. ## 기본 사용법 ``` const { data, error, isValidating, mutate } = useSWR(key, fetcher, options) ``` - parameter - key: 요청에 대한 unique 한 값 - fetcher: fetch 함수, promise를 return 하는 함수. - options : swr hook의 옵션 설정 - return values - data - error - isValidating - mutate(data?, shouldRevalidate?) : 캐시 데이터 변경을 위한 함수 ## Global configuration ```jsx= import useSWR, { SWRConfig } from 'swr' function Dashboard () { const { data: events } = useSWR('/api/events') const { data: projects } = useSWR('/api/projects') const { data: user } = useSWR('/api/user', { refreshInterval: 0 }) // override // ... } function App () { return ( <SWRConfig value={{ refreshInterval: 3000, fetcher: (resource, init) => fetch(resource, init).then(res => res.json()) }} > <Dashboard /> </SWRConfig> ) } ``` `<SWRConfig>`를 사용해서 global configuration을 할 수 있다. graphql을 사용할 경우 endpoint가 하나이기 때문에 fetcher를 global로 미리 등록을 해주어도 좋을 것 같다. ## Error handling `fetcher` 안에서 에러가 발생되면 hook에 의해서 `error`가 return 된다. `fetcher` 안에서 status code에 따라서 에러를 custom 해서 throw 할 수도 있다.[참고](https://swr.vercel.app/docs/error-handling#status-code-and-error-object) `onErrorRetry` 옵션을 통해서 에러가 발생했을 때 retry를 조절할 수 있다.[참고](https://swr.vercel.app/docs/error-handling#status-code-and-error-object) ## Conditional fetching `useSWR`의 첫번째 인자인 `key`에 falsy한 value가 주어지면 SWR이 request를 보내지 않는다. 함수나 조건부 삼항 연산자를 이용해서 fetching을 할 지 말지 결정할 수 있다. 또한 한 요청의 결과를 이용해 다음 요청을 보낸다면 serial fetching이 된다. ```jsx= function MyProjects () { const { data: user } = useSWR('/api/user') const { data: projects } = useSWR(() => '/api/projects?uid=' + user.id) // When passing a function, SWR will use the return // value as `key`. If the function throws or returns // falsy, SWR will know that some dependencies are not // ready. In this case `user.id` throws when `user` // isn't loaded. if (!projects) return 'loading...' return 'You have ' + projects.length + ' projects' } ``` ## pattern ```jsx function useUser (id) { const { data, error } = useSWR(`/api/user/${id}`, fetcher) return { user: data, isLoading: !error && !data, isError: error } } function Avatar ({ id }) { const { user, isLoading, isError } = useUser(id) if (isLoading) return <Spinner /> if (isError) return <Error /> return <img src={user.avatar} /> } ``` ### SWR을 사용했을 때와 사용하지 않았을 때의 차이 #### 사용하지 않았을 때 ```jsx // page component function Page () { const [user, setUser] = useState(null) // fetch data useEffect(() => { fetch('/api/user') .then(res => res.json()) .then(data => setUser(data)) }, []) // global loading state if (!user) return <Spinner/> return <div> <Navbar user={user} /> <Content user={user} /> </div> } // child components function Navbar ({ user }) { return <div> ... <Avatar user={user} /> </div> } function Content ({ user }) { return <h1>Welcome back, {user.name}</h1> } function Avatar ({ user }) { return <img src={user.avatar} alt={user.name} /> } ``` #### 사용했을 때 ```jsx // page component function Page () { return <div> <Navbar /> <Content /> </div> } // child components function Navbar () { return <div> ... <Avatar /> </div> } function Content () { const { user, isLoading } = useUser() if (isLoading) return <Spinner /> return <h1>Welcome back, {user.name}</h1> } function Avatar () { const { user, isLoading } = useUser() if (isLoading) return <Spinner /> return <img src={user.avatar} alt={user.name} /> } ``` 부모 컴포넌트에서 자식 컴포넌트로 어떤 데이터를 전달해야하는지 알 필요가 없다. 또한 자동으로 캐시를 하고 공유하기 때문에 request를 한 번만 보낸다. ## GraphQL - graphql-request 와 같은 libs와 같이 사용한다 ``` import { request } from 'graphql-request' const fetcher = query => request('/api/graphql', query) function App () { const { data, error } = useSWR( `{ Movie(title: "Inception") { releaseDate actors { name } } }`, fetcher ) // ... } ``` ## 여러가지 인수를 넣는 방법 - 배열로 여러가지 매개변수를 사용할 수 있다. ``` const {data} = useSWR(['/api/orders', user, ]fetcher); ``` ### object passing 만약 user 정보를 가지고 데이터를 fetching 해야하는 경우가 있다고 하면, ```jsx= const { data: user } = useSWR(['/api/user', token], fetchWithToken) // ...and pass it as an argument to another query const { data: orders } = useSWR(user ? ['/api/orders', user] : null, fetchWithUser) ``` 위와 같이 코드가 작성되는데, request의 key가 두 값의 combination이 된다. SWR은 render할 때마다 arguments를 얕은 비교를 하고 바뀌었으면 revalidation을 한다. 따라서 redering을 할 때마다 object를 다시 만드는 것은 좋지 않다. ```jsx= // Don’t do this! Deps will be changed on every render. useSWR(['/api/user', { id }], query) // Instead, you should only pass “stable” values. useSWR(['/api/user', id], (url, id) => query(url, { id })) ``` ## Mutation 같은 key를 가진 SWR들에게 revalidation message를 `mutate(key)`를 통해서 revalidation 할 수 있다. 대부분의 경우에 `mutate`를 사용해 local data를 업데이트 한 다음에 revalidation을 하는 것이 더 속도가 빠르다. ```jsx= import useSWR, { mutate } from 'swr' function Profile () { const { data } = useSWR('/api/user', fetcher) return ( <div> <h1>My name is {data.name}.</h1> <button onClick={async () => { const newName = data.name.toUpperCase() // update the local data immediately, but disable the revalidation mutate('/api/user', { ...data, name: newName }, false) // send a request to the API to update the source await requestUpdateUsername(newName) // trigger a revalidation (refetch) to make sure our local data is correct mutate('/api/user') }}>Uppercase my name!</button> </div> ) } ``` 만약 api가 업데이트 된 결과를 반환하면 다음과 같이 코드를 줄일 수 있다. ```jsx= mutate('/api/user', newUser, false) // use `false` to mutate without revalidation mutate('/api/user', updateUser(newUser)) // `updateUser` is a Promise of the request, // which returns the updated document ``` ### 현재 데이터를 기반으로 mutate mutate의 두 번째 인자로 async 함수를 넣으면 매개변수로 현재 캐시된 값을 받는다.[참고](https://swr.vercel.app/docs/mutation#mutate-based-on-current-data) ## 자동 재 검증 - Revalidate on Focus : 현재 페이지에 focus 되면 데이터를 다시 받아온다 - Revalidate on Interval : 정해진 시간 간격마다 데이터를 다시 받아온다. - `refreshInterval` 옵션을 사용해서 interval을 지정할 수 있다. - Revalidate on Reconnect : 다시 연결되었을 때 데이터를 다시 받아온다. ## 데이터 관리 - mutate를 사용하여 로컬 데이터를 최신으로 업데이트 하는 동시에 유효성을 검사하고, 마지막으로 최신 데이터로 바꿀 수 있다. - mutate를 swr로 불러서 사용했을 경우, key 값을 넣어줘야한다 ``` import {mutate} from 'swr'; ... mutate(key,{...data, name: newName}) ``` - useSWR에서 반환된 mutate에는 바인딩되어있어 key값이 필요하지 않다. ``` const {data, mutate} = useSWR(key, fetcher); mutate({...date, name : newName}, true); ``` 두번째 인자로 true 값을 주었을 때 먼저 newName를 캐시에 추가하고, DB와 비교해서 최신 값을 보여준다. false 로 주었을 때, revalidation을 하지 않는다. ## data prefetch - serverSideProps로 데이터를 미리 받아와 props로 내려줬을 때, initalData로 설정할 수 있다 ``` const { data: article, mutate: articleMutate } = useSWR( id ? `${URL}/${id}` : null, fetcher, { refreshInterval: 0, revalidateOnFocus: false, revalidateOnReconnect: false, initialData: JSON.parse(initalPostData), } ); ``` ## 성능 SWR은 캐싱과 deduplication을 통해서 불필요한 네트워크 비용을 줄인다. ### deduplication 같은 키를 가진 `useSWR` hook을 여러 곳에서 쓰는 경우가 많다. 그런 경우 하나의 네트워크 요청만 가도록 한다. ### data comparison SWR은 data 변화를 깊은 비교를 한다. data가 변하지 않으면 리렌더링이 되지 않는다. 만약 비교 함수를 customize 하고 싶으면 `compare` 옵션을 사용할 수 있다. ### dependency collection `useSWR`은 3개의 state를 나타내는 값을 가진다. 그리고 이 세가지는 독립적으로 업데이트가 된다. 따라서 다음과 같은 코드가 있을 때 `data`, `error`, `isValidating`의 변화는 5단계가 될 수 있다. 코드 ```jsx= function App () { const { data, error, isValidating } = useSWR('/api', fetcher) console.log(data, error, isValidating) return null } ``` 결과 ``` // console.log(data, error, isValidating) undefined undefined false // => hydration / initial render undefined undefined true // => start fetching undefined Error false // => end fetching, got an error undefined Error true // => start retrying Data undefined false // => end retrying, get the data ``` 그러면 component는 5번 리렌더링이 된다. 이 문제를 고치기 위해서 `data`만을 컴포넌트가 사용하도록 할 수 있다. 코드 ```jsx= function App () { const { data } = useSWR('/api', fetcher) console.log(data) return null } ``` 결과 ``` // console.log(data) undefined // => hydration / initial render Data // => end retrying, get the data ``` ## 참고 자료 [재남님 useSWR 무한스크롤 blog](https://roy-jung.github.io/201130_swr-graphql-infinite-scroll/) [SWR 공식 문서](https://swr.vercel.app/) [SWR use Hook](https://jaeseokim.tistory.com/113) ## 개인적 의견 - SWR을 사용해보지는 않아 러닝커브가 있을수도 있지만, 데이터를 revalidation 해주고 상태관리의 많은 부분을 swr이 해준다고 느꼈다. apollo를 둘다 사용해봤으면 apollo를 사용하는 것이 더 빠르게 프로젝트를 진행할 수 있다고 생각했지만, 둘중 한명이라도 안사용해봤기 때문에 swr을 사용해 보는것도 좋을 것 같다. ###### tags: `tech sharing`