QueryClientProvider의 역할
-> 아래 예제 코드를 보자.
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5분
retry: 1,
},
},
});
export function App() {
return (
<QueryClientProvider client={queryClient}>
<MyApp />
</QueryClientProvider>
);
}
여기서 QueryClientProvider의 역할은 무엇인가?
-> QueryClientProvider는 생성된 queryClient 인스턴스를 하위 컴포넌트 트리 전체에서 사용할 수 있도록 주입하는 역할을 한다. 이는 React의 Context API를 통해 동작한다.
만약에 실제로 QueryClientProvider 를 구현하게 된다면 실제 아래와 같은 예시일것이다.
// QueryClientProvider 내부 동작 (단순화)
const QueryClientContext = React.createContext<QueryClient | null>(null);
export function QueryClientProvider({ client, children }) {
return (
<QueryClientContext.Provider value={client}>
{children}
</QueryClientContext.Provider>
);
}
export function useQueryClient() {
const client = React.useContext(QueryClientContext);
if (!client) throw new Error('No QueryClient set');
return client;
}
Context API의 3단계 패턴과 완전히 동일하다.
┌──────┬──────────────────┬────────────────┐
│ 단계 │ API │ 역할 │
├──────┼──────────────────┼────────────────┤
│ 1 │ createContext() │ 저장 공간 생성 │
├──────┼──────────────────┼────────────────┤
│ 2 │ Context.Provider │ 값 주입 │
├──────┼──────────────────┼────────────────┤
│ 3 │ useContext() │ 값 꺼내기 │
└──────┴──────────────────┴────────────────┘
즉, QueryClientProvider는 이 패턴을 라이브러리가 대신 구현해준 것이다. useContext의 역할은 새로운 클라이언트를 만드는게 아니라 Provider에 저장된 값의 참조를 반환하기 때문에 항상 같은 인스턴스를 반환한다.
번외로 아래와 같이 소비하는 컴포넌트의 예제 코드이다.
import { useQueryClient, useMutation } from '@tanstack/react-query';
function TodoItem({ id }: { id: number }) {
const queryClient = useQueryClient(); // QueryClient 인스턴스 꺼내기
const deleteMutation = useMutation({
mutationFn: () => deleteTodo(id),
onSuccess: () => {
// 삭제 후 목록 캐시 무효화 → 자동 리페치
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
return (
<button onClick={() => deleteMutation.mutate()}>
삭제
</button>
);
}
QueryClient 생성 후, 아래와 같이 래퍼 컴포넌트로 커스터마이징해서 사용할 수도 있다.
function QueryClientWithAlerts({ children }: { children: ReactNode }) {
const alertApi = useApi(alertApiRef)
const alertApiLatest = useRef(alertApi)
alertApiLatest.current = alertApi
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
retry: 0,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
},
},
mutationCache: new MutationCache({
onError: error => {
alertApiLatest.current.post({
message: error instanceof Error ? error.message : '요청 처리 중 오류가 발생했습니다.',
severity: 'error',
})
},
}),
}),
)
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
}
순수한 QueryClientProvider를 그대로 노출하지 않고, 필요한 기능을 추가한 래퍼 컴포넌트로 감싸서 사용하는 패턴이다.