# 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`