본문 바로가기
내가 경험한 것/1.5

TanStack Query v5 의 Breaking Changes

by nr2p 2024. 1. 7.
반응형

내가 열심히 노는동안 언제나오나 싶었던 v5가 나왔다!

그것도 많은 변화를 가지고!! (문서 링크)

 

언제 다 마이그레이션 하나 싶지만 아무튼 알아보자.. ㅎ

모든 변경점을 적진 않았습니다.. ㅎ


1. Supports a single signature, one object

- useQuery 및 기타 등등 훅들은 아래와 같이 여러 형태로 사용되었는데 유지보수 및 타입스크립트 측면 등의 이유로 이제는 한가지 형태로 통일되었다.

> 사용하는 입장에서도 하나로 통일되는게 컨벤션 안 맞춰도 되고 좋은 것 같다.

// ASIS: 여러 방법으로 사용 가능
useQuery(queryKey, queryFn, options);
useQuery(queryKey, options);
useQuery(options);

// TOBE: 한가지 방법으로만 사용 가능
useQuery(options);

 

이런 것들도 바꼈다. 참고하시길..!

 

 

변경할 생각에 눈 앞이 아득해진다면? Codemod

 

- 고통받을 개발자들을 생각했는지 마이그레이션 편하게? 하는 방법을 추가해놓았다. 약간 감동이다. 몰랐는데 앞으로도 애용해야겠다.

 

npx jscodeshift@latest ./src/ \
  --extensions=ts,tsx \
  --parser=tsx \
  --transform=./node_modules/@tanstack/react-query/build/codemods/src/v5/remove-overloads/remove-overloads.js

 

ASIS

 

TOBE

 

잘 변경되는데 prettier, eslint autofix 를 해주진 않아서 스크립트 실행 후 Formatting 작업을 다시 해주긴 해야한다.

 


2. Callbacks on useQuery (and QueryObserver) have been removed (link)

- useQueryonSuccess, onError, onSetteled 같은 콜백 옵션이 사라졌다. 

 

 

 

- 나또한 이런 생각으로 callback 사용하는걸 좋아했다. 물론 useQuery 에는 Suspense 도입 이후 잘 안 썼지만 ㅎ

 

onError

global-cache level 에서 콜백을 세팅하기

- 쿼리당 한 번만 호출됨

// 서버의 에러메세지를 그대로 사용하는 경우
const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error) =>
      toast.error(`Something went wrong: ${error.message}`),
  }),
})

// custom error message 를 사용하고 싶은 경우
const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error, query) => {
      if (query.meta.errorMessage) {
        toast.error(query.meta.errorMessage)
      }
    },
  }),
})

export function useTodos() {
  return useQuery({
    queryKey: ['todos', 'list'],
    queryFn: fetchTodos,
    meta: {
      errorMessage: 'Failed to fetch todos',
    },
  })
}

 

기존에는 아래와 같이 사용했었다.

원래는 axios 의 interceptor 레벨에서 에러 토스트를 띄어주었으나, 에러 토스트를 커스텀하게 하고 싶을 때 할 수가 없어서
useQuery 의 글로벌 콜백 onError 에 넣어줬었고

공용 에러 토스트를 사용하고 싶지 않으면 각 쿼리에서 onError 콜백을 통해 overwrite 해버리게끔 처리했다.
const globalQueryClientConfig: QueryClientConfig = {
  defaultOptions: {
    queries: {
    ...생략
      onError: onGlobalQueryError,
    },
    mutations: {
    ...생략
      onError: onGlobalQueryError,
    },
  },
};

const onGlobalQueryError = (e) => {
  if (!e?.code) {
    return;
  }

  if (e.code < 0) {
    /** toast 겹치는 이슈로 각 api 에서 따로 에러 액션을 정의해서 사용 */
    toast.error(e.message);
  }
};

 

> 이 정도는 onGlobalQuery Error 위치만 변경해주면 돼서 쉽게 변경 가능할 것 같다.

 

isError flag 사용해서 처리하기

- 여기서부터는 이 url 을 참고했다

- 이렇게 사용할 때는 if문을 data 존재 여부 > error 존재 여부 > else 로더 순으로 해야 백그라운드 에러 관련 이슈를 해결할 수 있다. 하지만 항상 프로젝트 바이 프로젝트 이기 때문에 상황 봐서 알맞은 순서로 사용하면 된다.

function TodoList() {
  const todos = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos
  })

  if (todos.isPending) {
    return 'Loading...'
  }

  // ✅ standard error handling
  // could also check for: todos.status === 'error'
  if (todos.isError) {
    return 'An error occurred'
  }

  return (
    <div>
      {todos.data.map((todo) => (
        <Todo key={todo.id} {...todo} />
      ))}
    </div>
  )
}

 

ErrorBoundaries 사용

- throwOnError option 을 켜고 ErrorBoundary 로 덮어주면 끝

 

onSuccess

- 아래와 같이 onSuccess 에서 setState 를 하지 말라고 되어 있다. 왤까?

> 불필요한 렌더 사이클이 추가될 수도 있다.

> 렌더 사이클 중 잘못된 값으로 렌더링 될 수 있다.

> staleTime 사용시 onSuccess 가 항상 호출되지만은 않는다

export function useTodos() {
  const [todoCount, setTodoCount] = React.useState(0)
  const { data: todos } = useQuery({
    queryKey: ['todos', 'list'],
    queryFn: fetchTodos,
    //😭 please don't
    onSuccess: (data) => {
      setTodoCount(data.length)
    },
  })

  return { todos, todoCount }
}

 


3. useQuery 의 Return 값중 remove 제거

- query.remove 대신 queryClient.removeQueries({queryKey}) 사용

 


 

4. 최소 요구 버전 변경

- typescript 4.7

- react 18


4. 이름 변경

- cacheTime > gcTime

- useErrorBoundary > throwOnError

- status : loading > pending

- isLoading > isPending

- isInitialLoading > isLoading

- hasQueryKey > hasKey


5.  Error 의 타입 변경 (unknown > Error)

- 에러 타이핑 하기 (링크)

 


 

6.  retry 기본값 변경 3 > 0

 


7.  새로운 hook 추가

- useSuspenseQuery

- useSuspenseInfiniteQuery

- useSuspenseQueries

 

- 기존에 suspense 와 함께 사용할 때 data의 타입이 undefined 와 함께 추론되던 문제점을 해결했다.

 

그리고 이 hook 이 추가되면서 내가 useQuery 사용하던 패턴을 변경해야겠다는 생각이 들었다.

 

ASIS

useGetSomethingQuery 라고 useQuery함수를 option을 변경하끔 만들어놓고 사용

특수한 상황 제외하고 useGetSomethingQuery, queryKey 정도만 export 하여 사용

 

TOBE

QueryKey, QueryFn 을 export 하고 useQuery 구문은 그때그때 사용

만약 QueryKey와 QueryFn 을 함께 쓰고 싶다면 queryOptions 사용 (링크)

반응형