Migrating to React Query 4
Breaking Changes
v4는 주요 버전 업데이트로, 몇 가지 주요 변경 사항이 있습니다:
react-query is now @tanstack/react-query
이제 react-query
는 @tanstack/react-query
로 변경되었습니다. 따라서 종속성을 제거하고 새로운 종속성을 설치해야 합니다:
npm uninstall react-query
npm install @tanstack/react-query
npm install @tanstack/react-query-devtools
- import { useQuery } from 'react-query' // [!code --]
- import { ReactQueryDevtools } from 'react-query/devtools' // [!code --]
+ import { useQuery } from '@tanstack/react-query' // [!code ++]
+ import { ReactQueryDevtools } from '@tanstack/react-query-devtools' // [!code ++]
Codemod
이전의 import를 새 버전에 맞게 자동으로 변경하는 codemod가 제공됩니다. 이를 통해 import 변경 작업을 더 쉽게 할 수 있습니다.
코드 생성 후에는 반드시 검토해 주시고, 로그 출력을 주의 깊게 살펴보세요.
.js
또는 .jsx
파일에 적용하려면 다음 명령어를 사용하세요:
npx jscodeshift ./path/to/src/ \
--extensions=js,jsx \
--transform=./node_modules/@tanstack/react-query/codemods/v4/replace-import-specifier.js
.ts
또는 .tsx
파일에 적용하려면 다음 명령어를 사용하세요:
npx jscodeshift ./path/to/src/ \
--extensions=ts,tsx \
--parser=tsx \
--transform=./node_modules/@tanstack/react-query/codemods/v4/replace-import-specifier.js
TypeScript
의 경우 tsx
를 파서로 지정해야 합니다. 그렇지 않으면 codemod가 제대로 적용되지 않습니다.
Note: codemod를 적용한 후에는 코드 포맷팅이 깨질 수 있으므로 prettier
및/또는 eslint
를 실행하는 것을 잊지 마세요!
Note: codemod는 오직 import만 변경하며, devtools 패키지는 별도로 수동 설치해야 합니다.
Query Keys (and Mutation Keys) need to be an Array
v3에서는 Query 및 Mutation Keys를 문자열 또는 배열로 사용할 수 있었지만, React Query는 내부적으로 배열만 사용합니다. 모든 API에서 일관성을 유지하기 위해, 이제 모든 키는 배열만 허용됩니다:
-useQuery("todos", fetchTodos) + // [!code --]
useQuery(["todos"], fetchTodos); // [!code ++]
Codemod
이 변경을 쉽게 적용할 수 있도록 codemod가 제공됩니다. 이를 통해 배열로 변환할 수 있습니다.
.js
또는 .jsx
파일에 적용하려면 다음 명령어를 사용하세요:
npx jscodeshift ./path/to/src/ \
--extensions=js,jsx \
--transform=./node_modules/@tanstack/react-query/codemods/v4/key-transformation.js
.ts
또는 .tsx
파일에 적용하려면 다음 명령어를 사용하세요:
npx jscodeshift ./path/to/src/ \
--extensions=ts,tsx \
--parser=tsx \
--transform=./node_modules/@tanstack/react-query/codemods/v4/key-transformation.js
TypeScript의 경우 tsx
를 파서로 지정해야 합니다. 그렇지 않으면 codemod가 제대로 적용되지 않습니다.
Note: codemod를 적용한 후에는 코드 포맷팅이 깨질 수 있으므로 prettier
및/또는 eslint
를 실행하는 것을 잊지 마세요!
The idle state has been removed
새로운 fetchStatus
가 도입되면서 idle
상태는 더 이상 필요하지 않게 되었습니다. 이제 fetchStatus: 'idle'
로 동일한 상태를 더 잘 표현할 수 있습니다.
주로 disabled
쿼리에 영향을 미치며, 이전에는 idle
상태였던 쿼리는 이제 loading
상태로 표시됩니다:
- status: 'idle' // [!code --]
+ status: 'loading' // [!code ++]
+ fetchStatus: 'idle' // [!code ++]
disabled
쿼리는 이제 loading
상태로 시작합니다. 따라서 isLoading
대신 isInitialLoading
을 사용하는 것이 좋습니다:
-isLoading + // [!code --]
isInitialLoading; // [!code ++]
new API for useQueries
useQueries
훅은 이제 queries
prop이 있는 객체를 입력으로 받습니다. queries
prop의 값은 쿼리 배열입니다:
-useQueries([
{ queryKey1, queryFn1, options1 },
{ queryKey2, queryFn2, options2 },
]) + // [!code --]
useQueries({
queries: [
{ queryKey1, queryFn1, options1 },
{ queryKey2, queryFn2, options2 },
],
}); // [!code ++]
Undefined is an illegal cache value for successful queries
업데이트를 중단할 수 있도록 undefined
를 반환할 수 있지만, undefined
는 이제 유효한 캐시 값이 아닙니다. undefined
를 반환하는 것은 이제 허용되지 않으며, 런타임에서 실패한 Promise
로 변환되어 에러가 발생하고, 개발 모드에서는 콘솔에 기록됩니다.
Queries and mutations, per default, need network connection to run
기본적으로 쿼리와 뮤테이션은 네트워크 연결이 없으면 paused
상태가 됩니다. 이전 동작을 유지하려면 networkMode: offlineFirst
를 글로벌로 설정할 수 있습니다:
new QueryClient({
defaultOptions: {
queries: {
networkMode: "offlineFirst",
},
mutations: {
networkMode: "offlineFirst",
},
},
});
notifyOnChangeProps
property no longer accepts "tracked"
as a value
notifyOnChangeProps
옵션이 더 이상 "tracked"
값을 허용하지 않습니다. 대신, useQuery
는 기본적으로 속성을 추적합니다. notifyOnChangeProps: "tracked"
을 사용하는 모든 쿼리는 이 옵션을 제거해야 합니다.
notifyOnChangePropsExclusion
has been removed
v4에서 notifyOnChangeProps
는 기본적으로 "tracked"
동작을 따릅니다. 따라서 더 이상 notifyOnChangePropsExclusion
옵션을 사용할 필요가 없습니다.
Consistent behavior for cancelRefetch
cancelRefetch
옵션은 이제 모든 함수에서 사용되며, 기본값이 true
로 설정됩니다. refetchQueries
나 invalidateQueries
를 연속으로 호출하면 첫 번째 호출이 취소되고 두 번째 호출이 시작됩니다:
queryClient.refetchQueries({ queryKey: ['todos'] })
// 이 호출은 이전 refetch를 취소하고 새 fetch를 시작합니다
queryClient.refetchQueries({ queryKey: ['todos'] })
이 동작을 원하지 않으면 cancelRefetch: false
를 명시적으로 설정할 수 있습니다:
queryClient.refetchQueries({ queryKey: ['todos'] })
// 이 호출은 이전 refetch를 취소하지 않습니다
queryClient.refetchQueries({ queryKey: ['todos'] }, { cancelRefetch: false })
Query Filters
쿼리 필터는 이제 단일 필터로 통합되었습니다. 이 필터는 'active', 'inactive', 'all' 중 하나를 선택할 수 있습니다:
- active?: boolean // [!code --]
- inactive?: boolean // [!code --]
+ type?: 'active' | 'inactive' | 'all' // [!code ++]
이 필터는 기본값으로 all
을 사용하며, active
또는 inactive
쿼리만 선택할 수도 있습니다.
refetchActive / refetchInactive
queryClient.invalidateQueries
의 refetchActive
와 refetchInactive
플래그도 통합되었습니다:
- refetchActive?: boolean // [!code --]
- refetchInactive?: boolean // [!code --]
+ refetchType?: 'active' | 'inactive' | 'all' | 'none' // [!code ++]
이 플래그의 기본값은 active
이며, 모든 쿼리를 리페치하지 않으려면 none
옵션을 사용할 수 있습니다.
onSuccess
is no longer called from setQueryData
setQueryData
에서 onSuccess
가 호출되지 않습니다. 이제 onSuccess
는 요청이 있을 때만 호출됩니다. 데이터 필드의 변경 사항을 감지하려면 useEffect
를 사용할 수 있습니다:
const { data } = useQuery({ queryKey, queryFn });
React.useEffect(() => mySideEffectHere(data), [data]);
persistQueryClient
and the corresponding persister plugins are no longer experimental and have been renamed
createWebStoragePersistor
와 createAsyncStoragePersistor
는 각각 createSyncStoragePersister
와 createAsyncStoragePersister
로 이름이 변경되었습니다. persistQueryClient
는 이제 실험적이지 않으며 PersistQueryClient
로 리팩토링되었습니다:
- import { persistQueryClient } from 'react-query/persistQueryClient-experimental' // [!code --]
- import { createWebStoragePersistor } from 'react-query/createWebStoragePersistor-experimental' // [!code --]
- import { createAsyncStoragePersistor } from 'react-query/createAsyncStoragePersistor-experimental' // [!code --]
+ import { persistQueryClient } from '@tanstack/react-query-persist-client' // [!code ++]
+ import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister' // [!code ++]
+ import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister' // [!code ++]
The cancel
method on promises is no longer supported
기존의 cancel
메서드(구버전 cancel
메서드)는 더 이상 지원되지 않습니다. 이 메서드는 Promise에 cancel
함수를 정의하여 라이브러리가 쿼리 취소를 지원하도록 했으나 제거되었습니다. 이제는 AbortController
API (opens in a new tab)를 내부적으로 사용하여 AbortSignal
인스턴스 (opens in a new tab)를 제공하는 새로운 API (v3.30.0에서 도입됨)를 사용하는 것이 좋습니다.
TypeScript
타입은 이제 TypeScript v4.1 이상을 요구합니다.
Supported Browsers
v4부터 React Query는 최신 브라우저에 최적화되었습니다. 브라우저 리스트를 업데이트하여 더 현대적이고 성능이 뛰어나며 더 작은 번들을 생성하도록 했습니다. 요구 사항에 대해서는 여기에서 확인하실 수 있습니다.
setLogger
is removed
이전에는 setLogger
를 호출하여 전역적으로 로거를 변경할 수 있었습니다. v4에서는 이 함수가 QueryClient
를 생성할 때 선택적 필드로 대체되었습니다.
- import { QueryClient, setLogger } from 'react-query'; // [!code --]
+ import { QueryClient } from '@tanstack/react-query'; // [!code ++]
- setLogger(customLogger) // [!code --]
- const queryClient = new QueryClient(); // [!code --]
+ const queryClient = new QueryClient({ logger: customLogger }) // [!code ++]
No default manual Garbage Collection server-side
v3에서는 React Query가 쿼리 결과를 기본적으로 5분 동안 캐시한 후, 수동으로 가비지 컬렉션을 수행했습니다. 이 기본 설정은 서버 측 React Query에도 적용되었습니다.
이로 인해 높은 메모리 소비와 수동 가비지 컬렉션 완료를 기다리는 과정에서 프로세스가 멈추는 문제가 발생했습니다. v4에서는 기본적으로 서버 측의 cacheTime
이 Infinity
로 설정되어 수동 가비지 컬렉션이 비활성화되었습니다 (NodeJS 프로세스는 요청이 완료되면 모든 것을 자동으로 정리합니다).
이 변경 사항은 Next.js와 같은 서버 측 React Query 사용자의 경우에만 영향을 미칩니다. cacheTime
을 수동으로 설정하고 있다면 영향을 받지 않지만 (동작을 맞추는 것이 좋을 수 있습니다).
Logging in production
v4부터 react-query는 프로덕션 모드에서 콘솔에 오류(예: 실패한 요청)를 더 이상 기록하지 않습니다. 이는 많은 사용자에게 혼란을 초래했기 때문입니다. 오류는 여전히 개발 모드에서 표시됩니다.
ESM Support
React Query는 이제 package.json "exports"
(opens in a new tab)를 지원하며 Node의 기본 CommonJS 및 ESM 해상도와 완전히 호환됩니다. 대부분의 사용자에게는 주요 변경 사항이 없겠지만, 프로젝트에 가져올 수 있는 파일이 공식적으로 지원하는 진입점으로 제한됩니다.
Streamlined NotifyEvents
QueryCache
에 수동으로 구독할 때 QueryCacheNotifyEvent
를 받을 수 있었지만, MutationCache
에는 적용되지 않았습니다. 이제는 동작을 간소화하고 이벤트 이름도 이에 맞게 조정했습니다.
QueryCacheNotifyEvent
- type: 'queryAdded' // [!code --]
+ type: 'added' // [!code ++]
- type: 'queryRemoved' // [!code --]
+ type: 'removed' // [!code ++]
- type: 'queryUpdated' // [!code --]
+ type: 'updated' // [!code ++]
MutationCacheNotifyEvent
MutationCacheNotifyEvent
는 QueryCacheNotifyEvent
와 동일한 타입을 사용합니다.
참고: 이 내용은
queryCache.subscribe
또는mutationCache.subscribe
를 통해 캐시에 수동으로 구독하는 경우에만 관련이 있습니다.
Separate hydration exports have been removed
버전 3.22.0 (opens in a new tab)부터, hydration 유틸리티는 React Query 코어로 이동했습니다. v3에서는 여전히 react-query/hydration
에서 구버전 내보내기를 사용할 수 있었지만, v4에서는 이러한 내보내기가 제거되었습니다.
- import { dehydrate, hydrate, useHydrate, Hydrate } from 'react-query/hydration' // [!code --]
+ import { dehydrate, hydrate, useHydrate, Hydrate } from '@tanstack/react-query' // [!code ++]
Removed undocumented methods from the queryClient
, query
and mutation
QueryClient
의 cancelMutatations
와 executeMutation
메서드는 문서화되지 않았고 내부적으로 사용되지 않았기 때문에 제거되었습니다. 이 메서드는 mutationCache
에서 사용할 수 있는 메서드의 래퍼에 불과하므로, executeMutation
의 기능은 여전히 사용할 수 있습니다.
- executeMutation< // [!code --]
- TData = unknown, // [!code --]
- TError = unknown, // [!code --]
- TVariables = void, // [!code --]
- TContext = unknown // [!code --]
- >( // [!code --]
- options: MutationOptions<TData, TError, TVariables, TContext> // [!code --]
- ): Promise<TData> { // [!code --]
- return this.mutationCache.build(this, options).execute() // [!code --]
- } // [!code --]
추가로, query.setDefaultOptions
가 사용되지 않아 제거되었습니다. mutation.cancel
은 실제로 요청을 취소하지 않았기 때문에 제거되었습니다.
The src/react
directory was renamed to src/reactjs
이전에는 React Query가 react
라는 디렉토리를 가지고 있었고, 이는 react
모듈에서 가져왔습니다. 이는 일부 Jest 구성과 충돌을 일으킬 수 있어, 다음과 같은 테스트 오류를 초래할 수 있었습니다:
TypeError: Cannot read property 'createContext' of undefined
디렉토리 이름이 변경됨에 따라 더 이상 문제가 발생하지 않습니다.
프로젝트에서 'react-query/react'
에서 직접 가져오던 경우 (단순히 'react-query'
가 아닌 경우), 가져오기를 업데이트해야 합니다:
- import { QueryClientProvider } from 'react-query/react'; // [!code --]
+ import { QueryClientProvider } from '@tanstack/react-query/reactjs'; // [!code ++]
New Features 🚀
v4는 다음과 같은 멋진 새로운 기능들을 제공합니다:
Support for React 18
React 18이 올해 초에 출시되었으며, v4는 이제 이를 완벽히 지원하며 새로운 동시성 기능도 제공합니다.
Proper offline support
v3에서는 React Query가 쿼리와 변이를 실행한 후 인터넷에 연결되어 있어야 재시도할 수 있다고 가정했습니다. 이로 인해 여러 가지 혼란스러운 상황이 발생했습니다:
- 오프라인 상태에서 쿼리를 마운트하면 로딩 상태로 전환되고 요청이 실패하며, 오프라인 상태에서 다시 온라인 상태가 될 때까지 로딩 상태로 유지됩니다.
- 비슷하게, 오프라인 상태에서 재시도가 꺼져 있는 경우, 쿼리가 실행되었다가 실패하며 오류 상태로 전환됩니다.
- 네트워크 연결이 필요 없는 쿼리를 오프라인 상태에서 실행하고 싶을 때, 쿼리가 다른 이유로 실패하면 이제는 온라인 상태가 될 때까지 일시 중지됩니다.
- 창 포커스 재요청이 오프라인 상태에서는 아무런 동작을 하지 않았습니다.
v4에서는 이러한 문제를 해결하기 위해 새로운 networkMode
를 도입했습니다. 새로운 Network mode에 대한 페이지를 참조하여 더 많은 정보를 확인해 보세요.
Tracked Queries per default
React Query는 기본적으로 쿼리 속성을 "추적"하여 렌더링 최적화를 개선합니다. 이 기능은 v3.6.0 (opens in a new tab)부터 존재해왔으며, v4에서 기본 동작이 되었습니다.
Bailing out of updates with setQueryData
setQueryData의 함수형 업데이트자를 사용할 때, undefined
를 반환하여 업데이트를 중단할 수 있습니다. 이는 undefined
가 previousValue
로 제공되어 현재 캐시된 항목이 없고 생성할 수 없을 때 유용합니다. 예를 들어, 할 일을 토글하는 경우 다음과 같습니다:
queryClient.setQueryData(["todo", id], (previousTodo) =>
previousTodo ? { ...previousTodo, done: true } : undefined
);
Mutation Cache Garbage Collection
이제 mutation도 쿼리와 마찬가지로 자동으로 가비지 컬렉션에 의해 사라질 수 있습니다. 변이의 기본 cacheTime
은 5분으로 설정되어 있습니다.
Custom Contexts for Multiple Providers
여러 React Query Provider
인스턴스가 컴포넌트 트리에 있을 때, 훅과 맞는 Provider
를 지정할 수 있는 사용자 지정 컨텍스트를 설정할 수 있습니다. 이는 훅이 올바른 Provider
인스턴스를 사용하도록 보장하는 데 중요합니다.
예시:
- 데이터 패키지 생성.
// 첫 번째 데이터 패키지: @my-scope/container-data
const context = React.createContext<QueryClient | undefined>(undefined);
const queryClient = new QueryClient();
export const useUser = () => {
return useQuery(USER_KEY, USER_FETCHER, {
context,
});
};
export const ContainerDataProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
return (
<QueryClientProvider client={queryClient} context={context}>
{children}
</QueryClientProvider>
);
};
- 두 번째 데이터 패키지 생성.
// 두 번째 데이터 패키지: @my-scope/my-component-data
const context = React.createContext<QueryClient | undefined>(undefined);
const queryClient = new QueryClient();
export const useItems = () => {
return useQuery(ITEMS_KEY, ITEMS_FETCHER, {
context,
});
};
export const MyComponentDataProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
return (
<QueryClientProvider client={queryClient} context={context}>
{children}
</QueryClientProvider>
);
};
- 어플리케이션에서 두 데이터 패키지 사용.
// 어플리케이션
import { ContainerDataProvider, useUser } from "@my-scope/container-data";
import { AppDataProvider } from "@my-scope/app-data";
import { MyComponentDataProvider, useItems } from "@my-scope/my-component-data";
<ContainerDataProvider> // <-- 자신의 React Query provider를 사용하여 컨테이너 데이터(예: "user") 제공
...
<AppDataProvider> // <-- 자신의 React Query provider를 사용하여 어플리케이션 데이터 제공 (이 예제에서는 사용되지 않음)
...
<MyComponentDataProvider> // <-- 자신의 React Query provider를 사용하여 컴포넌트 데이터(예: "items") 제공
<MyComponent />
</MyComponentDataProvider>
...
</AppDataProvider>
...
</ContainerDataProvider>
// 위 "DataProvider" 컴포넌트에서 제공하는 훅의 예:
const MyComponent = () => {
const user = useUser() // <-- ContainerDataProvider에서 지정한 컨텍스트를 사용합니다.
const items = useItems() // <-- MyComponentDataProvider에서 지정한 컨텍스트를 사용합니다.
...
}