React-Query

Guides&Concepts_(Window Focus Refetching & Disabling/Pausing Queries)

ecoEarth 2023. 3. 21. 03:42

Window Focus Refetching

만약 유저가 다른 창으로 이동했고, 그 사이에 query data가 stale상태로 변해있다면 TanStack Query는 자동적으로 background환경에서 데이터를 요청한다. 만약 이게 싫다면 refetchOnWindowFocus option을 설정하여 개별 쿼리든, 전역적으로 설정해서 모든 쿼리에 대해 서든 이러한 설정을 막을 수 있다.

 

전역적으로 모든 query에 대해 window focus refetching 막기

 const queryClient = new QueryClient({
   defaultOptions: {
     queries: {
       refetchOnWindowFocus: false,
     },
   },
 })
 
 function App() {
   return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
 }
  • 이 예시에서 중요하게 봐야할 점은 전역적으로 window focus refetching을 막을 수 있다는 점 뿐 아니라, 전역적인 쿼리의 설정은
    QueryClientProvider를 제공하는 곳에서 설정할 수 있다는 점이다.

 

개별 query단위로 window focus refetching 막기

 useQuery('todos', fetchTodos, { refetchOnWindowFocus: false })

 

Custom Window Focus Event

드문 상황이긴 하겠지만 개발자 자신의 window focus events를 이용해 query를 revalidate하고싶을 수 있다. 그렇게 하기 위해서는

TanStack Query가 제공하는 'focusManager.setEventListener'함수를 이용하면된다. 이 함수는 window가 다시 focused되었을 때 실행되는 콜백함수를 제공해주고, 개발자가 설정한 이벤트를 셋업할 수 있도록 도와준다.  'focusManager.setEventListener'가 실행되면 이전에 설정된 'focusManager.setEventListener'핸들러는 무효화되고, 새로운 'focusManager.setEventListener'핸들러가 사용된다.

 focusManager.setEventListener(handleFocus => {
   // Listen to visibilitychange and focus
   if (typeof window !== 'undefined' && window.addEventListener) {
     window.addEventListener('visibilitychange', handleFocus, false)
     window.addEventListener('focus', handleFocus, false)
   }
 
   return () => {
     // Be sure to unsubscribe if a new handler is set
     window.removeEventListener('visibilitychange', handleFocus)
     window.removeEventListener('focus', handleFocus)
   }
 })

 

Ignoring Iframe Focus Events

iframe에 대한 이벤트 핸들러를 설정하는 것은 focusManager.setEventListener 함수를 이용하여 할 수 있다. iframe은 윈도우 포커스 이벤트를 감지하는 데 문제가 있다. 이벤트가 두 번 발생하거나 앱 내에서 iframe을 포커스하거나 사용할 때 가짜 이벤트가 발생하는 등의 형태로 나타날 수 있다. 이러한 문제가 발생할 경우, 가능한 한 이벤트를 무시하는 이벤트 핸들러를 사용하는 것이 좋다.

 import { focusManager } from 'react-query'
 import onWindowFocus from './onWindowFocus' // The gist above
 
 focusManager.setEventListener(onWindowFocus) // Boom!

 

Managing focus state

 import { focusManager } from 'react-query'
 
 // Override the default focus state
 focusManager.setFocused(true)
 
 // Fallback to the default focus check
 focusManager.setFocused(undefined)

 

alert() or file uplaod 기능으로 원치않는 refetching이 일어날 때 참고

Some browser internal dialogue windows, such as spawned by alert() or file upload dialogues (as created by <input type="file" />) might also trigger focus refetching after they close. This can result in unwanted side effects, as the refetching might trigger component unmounts or remounts before your file upload handler is executed. See this issue on GitHub for background and possible workarounds.


Disabling/Pausing Queries

만약 쿼리가 자동적으로 데이터를 받아오는 것을 항상 멈추고싶다면, enabled = false 옵션을 주면 된다.

enabled = false이 설정되면

  • 쿼리에 캐시된 데이터가 있으면, 쿼리는 초기화되고 status === 'success' 또는 isSuccess 상태가 된다.
  • 쿼리에 캐시된 데이터가 없으면, 쿼리는 status === 'loading'과 fetchStatus === 'idle' 상태에서 시작된다.
  • 쿼리는 마운트 시 자동으로 데이터를 가져오지 않는다.
  • 쿼리는 백그라운드에서 자동으로 데이터를 다시 가져오지 않는다.
  • 쿼리는 보통 invalidateQueries되거나 refetchQueries명령을 무시한다.
  • useQuery에서 반환되는 refetch를 사용하여 수동으로 쿼리를 다시 가져올 수 있다.
 function Todos() {
   const {
     isIdle,
     isLoading,
     isError,
     data,
     error,
     refetch,
     isFetching,
   } = useQuery('todos', fetchTodoList, {
     enabled: false,
   })
 
   return (
     <>
       <button onClick={() => refetch()}>Fetch Todos</button>
 
       {isIdle ? (
         'Not ready...'
       ) : isLoading ? (
         <span>Loading...</span>
       ) : isError ? (
         <span>Error: {error.message}</span>
       ) : (
         <>
           <ul>
             {data.map(todo => (
               <li key={todo.id}>{todo.title}</li>
             ))}
           </ul>
           <div>{isFetching ? 'Fetching...' : null}</div>
         </>
       )}
     </>
   )
 }

 

Lazy Queries

enabled 옵션은 쿼리를 영구적으로 비활성화하는 데만 사용하는 것이 아니라, 나중에 쿼리를 활성화/비활성화하는 데에도 사용할 수 있습니다. 예를 들어, 필터 폼에서는 사용자가 필터 값을 입력한 후에만 첫 번째 요청을 보내도록 하고 싶을 수 있습니다.

이 경우, 처음에는 enabled=false로 설정하여 쿼리를 비활성화하고, 필터 폼에 입력한 값을 캐시해 둡니다. 그리고 필터 값이 제출되면, enabled=true로 변경하여 쿼리를 활성화하고 필터 값을 사용하여 데이터를 가져옵니다. 이렇게 하면 사용자가 필터 폼에서 입력한 값을 기반으로 처음 요청을 수행할 수 있습니다. 또한 필터 값을 변경하면, 이전 결과가 캐시되어 있으므로, 다시 가져오지 않고 기존 결과를 사용하여 새로운 필터 값에 대한 데이터를 쉽게 가져올 수 있습니다.

이와 같은 방식으로 enabled 옵션을 사용하면, 쿼리를 동적으로 활성화/비활성화할 수 있으므로, 더욱 유연하고 효율적인 앱을 구현할 수 있습니다.

function Todos() {
  const [filter, setFilter] = React.useState('')

  const { data } = useQuery({
      queryKey: ['todos', filter],
      queryFn: () => fetchTodos(filter),
      // ⬇️ disabled as long as the filter is empty
      enabled: !!filter
  })

  return (
      <div>
        // 🚀 applying the filter will enable and execute the query
        <FiltersForm onApply={setFilter} />
        {data && <TodosTable data={data}} />
      </div>
  )
}

 

isInitialLoading

"lazy queries"가 처음 시작할 때 "loading" 상태가 될 것이며, "loading"은 아직 데이터가 없다는 것을 의미하기 때문에 그것이 기술적으로 맞는 것이라고 설명합니다. 그러나, 현재 데이터를 가져오지 않기 때문에 (쿼리가 활성화되지 않았기 때문에) 이 플래그를 사용하여 로딩 스피너를 표시할 수 없을 가능성도 있다는 것을 의미합니다."disabled"나 "lazy" 쿼리를 사용하는 경우, "isInitialLoading" 플래그를 대신 사용할 수 있음을 설명합니다. 이 플래그는 "isLoading && isFetching"에서 계산된 파생 플래그이므로, 쿼리가 현재 처음으로 가져 오고 있는 경우에만 true가 됩니다.