프로젝트를 진행하면서 유저 검색기능을 만들었다.
유저를 검색하면 미리 만들어둔 Profile.js
컴포넌트로 만들어진 프로필목록이 아래 사진처럼 주르륵 뜨게 된다.
해당 Profile.js
컴포넌트는 클릭하게 되면 바로 해당 유저의 마이페이지로 이동을 시켜주는 onClick 이벤트가 걸려있는 상태이다.
그.런.데
다른 페이지에서는 잘만 작동하던 이벤트가 여기서는 동작하지 않았다.
심지어 console 창에서는 아무런 에러 문구도 나오지 않았다. 😠 절망하면서 디버깅을 시작했다.
나중에는 다행히 페이지 이동 문제는 해결됐는데, 원하던 대로 onBlur 이벤트를 적용하는 것이 또 말을 안들었다.
처음에는 useNavigate 에서 내가 모르는 문제가 있는건가 하고 찾기 시작했다. 하지만 아무리 찾아도 알 수가 없었다.
그러다 onBlur 이벤트에 집중하기 시작하자 해결책을 찾을 수 있었다. onBlur 로 인해서 저 프로필 검색결과물이 나오는 박스를 클릭하는 순간 포커스가 벗어난 걸로 이벤트를 처리, 해당 프로필 컴포넌트가 들어있는 result box 가 사라지고 내 클릭이벤트는 허공을 누르는 것으로 처리되는 것이다.
그래서 onBlur 이벤트를 지우자 문제없이 프로필을 클릭하면 원하던대로 프로필 마이페이지로 이동했다.
그리고 onBlur 효과를 제대로 적용하기 위해서 상위 컴포넌트로 이동시켰다.
그런데,,, 두 번째 문제가 발생했다. onBlur 가 동작하지 않는 것이다. 무조건 유저검색 버튼을 다시 눌러서 state 를 바꿔주지 않는 한, 다른 부분을 누르면서 포커스가 해제됨과 동시에 state를 바꿔주는 onBlur 이벤트가 제대로 동작하지 않았다.
공식문서도 보고 이리저리 찾아다니다가 이유를 찾게 해준 천사같은 포스트 를 발견했다.
onBlur 이벤트와 onClick 이벤트를 같이 쓰면 순서가 꼬일 수 있다는 것이다!
이벤트 진행 순서: onBlur -> onClick
그래서 위의 포스트에서는 onClick 대신에 onMouseDown 으로 바꿀 것을 추천해줬다.
이벤트 진행 순서: onMouseDown -> onBlur -> onClick
그렇게 디버깅은 무사히 끝났다.
이 과정에서 배운 것을 정리하기 위해서 급하게 글을 남기고 있다.
공식문서에서 onBlur 와 onFocus 동작 알아보기
아래의 코드를 본인의 리액트 프로젝트에서 넣고 테스트를 바로 해볼 수 있다.
뭐, 언제나 그렇듯이 공식문서만 봐서는 바로 이해가 안되기에 무지성으로 일단 복붙해서 확인해봤다.
event.target 에 대한 설명
function Example() {
return (
<div
tabIndex={1}
onFocus={(e) => {
if (e.currentTarget === e.target) {
// div 영역을 클릭하면 트리거된다.
// child 포커스는 트리거되지 않는다
console.log('focused self');
} else {
// 그리고 그때 클릭한 자식영역도 나온다.
// input 중에 내가 누른 input 의 아이디가 콘솔에 뜬다
// 다른 input 으로 이동해서 클릭하면
// div 에 대한 포커스는 유지되고
// 새로이 클릭한 input 의 아이디가 출력되면서 포커스가 이동한 것을 알 수 있다.
console.log('focused child', e.target);
}
if (!e.currentTarget.contains(e.relatedTarget)) {
// 자식영역인 input 을 클릭하면 트리거된다
console.log('focus entered self');
}
}}
onBlur={(e) => {
if (e.currentTarget === e.target) {
// div 영역에서 벗어난 곳을 클릭하는 순간 트리거된다
console.log('unfocused self');
} else {
// 다른 input 을 클릭하면 원래 클릭했던 input 의 id 는
// 포커스가 풀렸다고(unfocused) 라고 나온다.
console.log('unfocused child', e.target);
}
if (!e.currentTarget.contains(e.relatedTarget)) {
// 자식 영영 (input) 을 아무리 바꿔 클릭해도 트리거 되지 않는다.
console.log('focus left self');
}
}}
>
<input id="1" />
<input id="2" />
</div>
);
}
그리고 테스트를 해봤다.
(velog 는 동영상 삽입이 안되는 바람에 gif 로 바꾸는 바람에 엄청 작아졌지만...)
위에서 보면 div
안에 들어있는 두개의 input
자식에 대해서 어떻게 동작하는지 보인다.
코드와 같이 어떻게 동작하는지 주석을 달아놓았다.
그 결과를 참고해서 아래의 코드처럼 검색박스가 나타나는 곳을 수정하고
const loseFocus = () => {
setResult('')
props.setIsSearching(false)
}
return (
<div className="search-box" ref={ resultBox }
onBlur={(e) => {
if (!e.currentTarget.contains(e.relatedTarget)) {
loseFocus();
}
}}
>
<input type="text"
onChange={(e) => search(e.target.value)}
placeholder="찾고싶은 유저명을 검색해주세요" autoFocus />
{
result && result.length ?
<div className="result-box">
{result.map(user => {
return <div key={ user.username }
onClick={() => {props.setIsSearching(false)}} ><Profile user={ user } ></Profile></div>
})}
</div> :
null
}
</div>
)
}
프로필을 클릭하면 페이지를 이동하는 이벤트 트리거를 onClick
에서 onMouseDown
으로 바꿨다
return (
// <div className="profile" onClick={() => {dispatch(setViewType(0)); navigate(`/mypage/${user.username}`);}}>
<div className="profile" onMouseDown={() => {dispatch(setViewType(0)); navigate(`/mypage/${user.username}`);}}>
{
user.profileURL ? <img className="profile-header" src={user.profileURL} alt="" />
: <img className="profile-header" src={defaultProfile} alt='default profile'/>
}
<div className='profile-nickname'>{user.nickname}</div>
</div>
)