무엇
focusManager는 TanStack Query가 "창이 포커스를 받았다"고 판단하는 방식을 관리하는 전역 싱글턴이다. 기본은 브라우저의 visibilitychange 이벤트고, 필요하면 이 판단 로직 자체를 갈아 끼울 수 있다.
인스턴스를 새로 만들 필요 없이 바로 import해서 쓴다.
import { focusManager } from '@tanstack/react-query'
무슨 일이 일어나나
포커스 상태가 바뀌면 QueryClient가 이를 지켜보다가, refetchOnWindowFocus가 켜진 쿼리를 다시 페치한다. window나 visibilitychange가 없는 환경(React Native 등)에서는 기본 감지가 아예 동작하지 않으므로, setEventListener로 감지 로직을 통째로 바꿔야 한다. setFocused로 값을 강제로 고정할 수도 있는데, undefined를 넘기면 다시 기본 감지로 돌아간다.
사용법
커스텀 이벤트 소스를 붙일 때는 setEventListener를 쓴다. handleFocus 함수를 받아 리스너를 등록하고, 해제 함수를 돌려준다.
import { focusManager } from '@tanstack/react-query'
focusManager.setEventListener((handleFocus) => {
if (typeof window === 'undefined' || !window.addEventListener) return () => {}
window.addEventListener('visibilitychange', handleFocus, false)
return () => {
window.removeEventListener('visibilitychange', handleFocus)
}
})
포커스 상태를 코드로 직접 지정할 수도 있다. 테스트나 특수 환경에서 쓴다.
focusManager.setFocused(true) // 강제로 포커스됨
focusManager.setFocused(undefined) // 기본 감지로 복귀
실무 예시
React Native에서는 window 이벤트가 없으므로, AppState로 감지 로직을 갈아 끼우는 게 정석 패턴이다.
import { AppState, Platform } from 'react-native'
import { focusManager } from '@tanstack/react-query'
if (Platform.OS !== 'web') {
focusManager.setEventListener((handleFocus) => {
const subscription = AppState.addEventListener('change', (status) => {
handleFocus(status === 'active')
})
return () => subscription.remove()
})
}
왜 중요한가
웹 앱이라면 기본 감지로 충분해서 이 매니저를 열어볼 일이 거의 없다. React Native나 Electron처럼 visibilitychange가 없는 런타임으로 넘어가는 순간에만 필요해지는 좁은 커스터마이징 지점이다. 평소엔 존재를 몰라도 되다가, 필요해지면 정확히 여기를 찾아야 하는 종류의 API다.
Reference