쿼리에 대한 초기 데이터가 필요한 경우 useQuery에 사용할 수 있는 두 가지 옵션으로 initialData, placeholderData가 있다. 이 둘에 대해 알아보고 차이점 까지 정리해 본다.
서비스에서 사용 가능한 쿼리에 대한 초기 데이터가 있고, 이를 쿼리에 직접 제공하고 싶다면, initialData 를 사용할 수 있다.
const query = useQuery({
queryKey: ['temp'],
queryFn: () => fetch('/temp'),
initialData: initialTempData,
})
다만 initialData에 설정되는 값은 query 데이터 업데이트 시에 가져오는 값과 동일한 구조를 지녀야 한다. 즉 부분적이거나 불완전한 데이터를 사용하는 것은 피해야 한다. 이는 intialData가 캐시에 유지되기 때문이다.
initialData가 캐시에 유지된다는 것은 initialData가 staleTime에 영향을 받는다는 것을 의미한다.
이러한 문제를 해결하기 위해 initialDataUpdateAt 옵션을 사용하는 방법이 존재한다.
initialDataUpdateAt은 staleTime을 원래 목적에 맞게 사용하여 데이터가 얼마나 최신이어야 하는지 결정하도록 하면서, initialData가 staleTime보다 오래된 경우 마운트 시에 데이터를 다시 가져올 수 있도록 한다.
const query = useQuery({
queryKey: ['temp'],
queryFn: () => fetch('/temp'),
initialData: initialTempData,
staleTime: 60 * 1000,
initialDataUpdateAt: initialTempDataUpdatedTime,
})
initialDataUpdateAt에는 Date.now() 가 제공하는 것과 같이 초기 데이터 자체가 마지막으로 업데이트된 시점의 숫자(js 타임스탬프)를 밀리초 단위로 전달 할 수 있다.
(Unix 타임스탬프의 경우 1000을 곱해 js 타임스탬프로 변환해야 한다)
initialData를 모든 헨들링에서 다시 가져오게 하지 않기 위해서 함수를 initialData 값으로 전달하도록 할 수 있다. 함수로 전달된 initialData는 쿼리가 초기화 될 때 한번만 실행되어 메모리 및 cpu를 절약한다.
const query = useQuery({
queryKey: ['temp'],
queryFn: () => fetch('/temp'),
initialData: () => genInitialTempData(),
})
사용하고자 하는 두 쿼리 데이터의 구조가 같은 경우 캐시된 다른 쿼리의 데이터를 특정 쿼리의 initialData로 사용할 수도 있다.
const queryClient = useQueryClient()
const query = useQuery({
queryKey: ['item', itemId],
queryFn: () => fetch(`/items/${itemId}`),
initialData: () => {
return queryClient.getQueryData(['items'])?.find(item=>item.id === itemId)
},
// 캐시를 이용한 경우에도 initialDataUpdateAt을 사용할 수 있다.
initialDataUpdateAt: () => {
return queryClient.getQueryState(['itams'])?.dateUpdatedAt,
}
})
initialData에 참조되는 원본 쿼리가 오래된 경우 사용하지 않고, 다시가져오기 위해서 queryClient.getQueryState
메서드를 활용해 원본 쿼리의 최신 여부를 식별할 수 있다.
const queryClient = useQueryClient()
const query = useQuery({
queryKey: ['item', itemId],
queryFn: () => fetch(`/items/${itemId}`),
initialData: () => {
const queryState = queryClient.getQueryState(['items'])
if(queryState && Date.now() - queryState.dataUpdatedAt <= n * 1000) {
return queryState.data.find(item=>item.id === itemId)
}
},
})
initialdata 옵션과 유사하게 쿼리가 이미 데이터가 있는 것처럼 작동하게 하지만, 캐시에는 유지되지 않는 방법이다.
이는 실제 데이터를 백그라운드에서 가져오는 동안 쿼리를 성공적으로 렌더링하기에는 충분한 부분 데이터가 있는 상황에 유용하게 사용할 수 있다.
placeholder를 사용하면 쿼리는 표시할 데이터가 존재 하기에 보류 상태가 되지 않고 성공 상태로 시작하게 된다. 실제 데이터와 구별하기 위해 쿼리 결과에서 isPlaceholderData 플래그가 true로 설정된다.
const placeholderData = useMemo(()=>genPlaceholderData(),[]);
// 그냥 값을 사용할 수 있지만 placeholderData는 렌더링마다 값을 새로이 가져오기 때문에 한번만 가져오도록 하고 싶다면 useMemo를 사용해보는 것이 좋다.
const query = useQuery({
queryKey: ['temp'],
queryFn: ()=>fetch('/temp'),
placeholderData,
})
placeholderData는 이전에 성공한 쿼리의 데이터 및 쿼리 메타 정보에 접근하는 함수로 만들 수 도 있다.
이전 쿼리의 데이터를 다른 쿼리의 placeholderData로 사용하려는 상황에 주로 사용되며, queryKey가 변경되면 데이터가 이전 쿼리에서 다른 쿼리로 전환되는 동안 전환 이전의 쿼리 데이터를 표시해 놓을 수 있다.
const query = useQuery({
queryKey: ['temp', id],
queryFn: () => fetch(`/temp/${id}`),
placeholderData: (previousData, previousQuery) => previousData,
})
함수형과 유사하게 캐시된 다른 쿼리의 데이터를 특정 쿼리의 placeholderData로 사용할 수 있다.
예를 들어 list query의 데이터에서 item query 데이터에 동일하게 사용되는 title이나, description 등을 item query의 placeholderData로 사용하는 방법이 있다.
const queryClient = useQueryClient();
const query = useQuery({
queryKey: ['item', itemId],
queryFn: ()=>fetch(`/itemList/${itemId}`),
placeholderData: () => {
return queryClient.getQueryData(['itemList'])?.find(item=>item.id === itemId)
},
})
Reference