# [ 살펴보기 ] React Query - QueryKey

React Query는 fetch한 데이터의 cache를 queryKey를 통해 구분하기 때문에 useQuery를 통해 data를 fetch 할 때 해당 data에 따라 queryKey를 어떻게 설정할지에 대해 신경을 써야하며 application을 구성하는 전체 프론트엔드 코드에서 queryKey를 어떻게 관리할지에 대한 적절한 전략을 구비해두는 것이 좋다

TanStack Query ( React Query )의 core member인 Dominik의 blog에 게시된 [Effective React Query Keys](https://tkdodo.eu/blog/effective-react-query-keys)라는 포스팅을 보며 React Query를 사용할 때 어떤식으로 query key를 구성하는 것이 좋은지 살펴보자

## QeuryKey

만일 pagination을 필요로하는 list data를 fetch한다면 일반적으로 queryKey는 아래와 같을 것이다

```typescript
 const { data } = useQuery({
    queryKey: ["user", "list", { page: 1, limit: 10 }],
    queryFn: () => {},
 });
```

또는 특정 user 정보를 fetch하는 query라면 queryKey를 다음과 같이 구성할 수도 있을 것이다

```typescript
 const { data } = useQuery({
    queryKey: ["user", "detail", { id: 1 }],
    queryFn: () => {},
  });
```

위의 예제들에서 queryKey를 구성할 때 사용되는 규칙이 있음을 볼 수 있을 것이다, 먼저 queyKey에 가장 먼저 오는 값은 fetch하는 데이터가 어떤 데이터인가를 나타내며 ( 예제에서는 user ) 두 번째 값은 이 데이터가 list 데이터인지 혹은 단일 데이터인지 ( 예제에서는 detail ) 그리고 data fetch에 필요한 filter 값이 object 형태로 전달되고 있다.

queryKey만 따로 빼서 본다면 아래와 같을 것이다

```typescript
['user', 'list', {page:1, limit:10}]
// user list, filter값은 page가 1 limit이 10인 데이터
['user', 'list', {page:2, limit:10}]
// user list, filter값은 page가 2 limit이 10인 데이터
['user', 'detail', {id:1}]
// 단일 user, filter값은 id가 1인 데이터
['user', 'detail', {id:2}]
// 단일 user, filter값은 id가 2인 데이터
```

queryKey를 위와 같은 형태로 구성하면 'user'와 관련된 cache데이터를 한결 수월하게 invalidate할 수 있다.

예를 들어 filter option값이 id 1인 user detail cache와 같이 상세한 cache 데이터를 invalidate하고 싶다면 다음과 같이 invalidate 시킨다

```typescript
const queryClient = useQueryClient();
queryClient.invalidateQueries({
  queryKey: ['user', 'detail', {id:1}]
})
```

혹은 다음 예제와 같이 filter option과 무관하게 모든 user detail 데이터를 invalidate 시킬 수도 있다. 아래와 같이 query를 invalidate하면 queryKey가 \['user', 'detail'\]로 시작하는 모든 cache 데이터를 invalidate한다

```typescript
const queryClient = useQueryClient();
queryClient.invalidateQueries({
  queryKey: ['user', 'detail']
})
```

만약 list 또는 detail 타입과는 상관없이 user로 시작하는 모든 cache 데이터를 invalidate하고 싶다면 다음과 같이 queryKey에 user만 넘기면 된다

```typescript
const queryClient = useQueryClient();
queryClient.invalidateQueries({
  queryKey: ['user']
})
```

만약 queryKey를 설정할 때 위와 같은 체계 없이 query에 따라 모두 제각각 설정 되고 있다면 특정 유저과 관련된 모든 cache data를 ( 위의 예제로 치면 user detail ) 모두 invalidate해야 하는 상황에 맞닥드리면 굉장히 난감해 질 수 있다

하지만 queryKey가 모두 위와 같이 관리되고 있다면 \['user', 'detail'\] queryKey만 invalidate 시키면 되니 cache 관리가 훨씬 수월해 진다

## Querykey Factories

실제로 queryKey를 설정할 때 위 처럼 하드코딩으로 직접 넣는 것 보다는 query domain에 맞는 query key를 담당하는 객체를 하나 만들어서 관리하는 것이 좋다

user라는 데이터를 기준으로 querykey factory 객체를 만든다면 다음과 같은 형식이 될 것이다

```typescript
const userKeys = {
  root:['user'],
  detail:['user', 'detail'],
  list:['user', 'list'],
  detailWithFiler:(filter:UserDetailFilterType)=> [...userKeys.detail, filter],
  listWithFilter:(filter:UserListFilterType)=> [...userKeys.list, filter],
}
```

그리고 invalidate시 다음과 같이 사용할 수 있을 것이다

```typescript
// ['user', 'detail'] key로 시작하는 모든 cache invalidate
queryClient.invalidateQueries({
  queryKey: userKeys.detail
})

// ['user', 'detail', {id:1}] key를 가진 cache invalidate
queryClient.invalidateQueries({
  queryKey: userKeys.detailWithFiler({id:1}_
})
```

위에서 소개한 blog를 쓴 Dominik이 이야기 하고 있듯이 위의 방법은 무조건 따라야 하는 rule은 아니다. 하지만 필자는 해당 방법이 좋다고 생각하여 queryKey를 구성할 때 위와 같은 방법을 따르려고 노력하고 있다.
