iOS 모바일 onBlur 처리 순서
3 minute read
2024-08-21
iOS 모바일에서 form 에 바깥을 클릭했을 때 confirm 으로 확인하도록 로직을 작성했다. 그래서 아래와 같은 커스텀 훅을 작성하면 될 것 같았다.
function useFormFocusState() {
const [isAnyFocus, setIsAnyFocus] = useState(false)
const checkFocus = useCallback(() => setIsAnyFocus(true), [])
const uncheckFocus = useCallback(() => setIsAnyFocus(false), [])
return {
isAnyFocus,
onFocus: checkFocus,
onBlur: uncheckFocus,
}
}그러고 기쁜 마음에 컴포넌트에 적용해보았다.
function MyComponent() {
const { isAnyFocus, ...anyFocusProps } = useFormFocusState()
const handleOpenChange = useCallback(
(open: boolean) => {
if (!open) {
// 닫을려고 할때
if (isAnyFocus) {
// NOTE: 해당 요소의 onBlur 에서 직접 처리. 여기서 호출 X
return
}
const result = confirm(
'입력하신 정보가 저장되지 않습니다.\n취소하시겠습니까?'
)
if (result) {
setOpen(false)
form.reset()
return
}
} else {
setOpen(true)
}
},
[isAnyFocus, form]
)
return (
<Modal onOpenChange={handleOpenChange}>
{/* ... */}
<FormControl {...anyFocusProps}>
</Modal>
)
}PC에서 확인했을 때, 의도한 대로 동작했다. 끝난 줄 알았다.
하지만, 모바일에선 바로 confirm 이 먼저 나왔다. window.alert() 로 확인해보니 순서가 아래와 같이 진행되었기 때문에 문제가 되었다.
focus
-> (isAnyFouc true)
-> 바깥 클릭
-> (isAnyFocus false)
-> onOpenChange()개선 코드
function useFormFocusState() {
// ...
const uncheckFocus = useCallback(() => {
const isIos = /iPhone|iPad|iPod/i.test(navigator.userAgent)
if (isIos) {
setTimeout(() => setIsAnyFocus(false), 0)
} else {
setIsAnyFocus(false)
}
}, [])
// ...
}비동기로 처리되는 상태 변경 흐름에서 후순위 로 처리하기 위해 macro queue 에 콜백이 적재되도록 setTimeout 을 호출해서 해결하였다.