자란다 과제를 배포 전에 코드 정리를 하면서 의문점을 들게 한 코드를 찾게 되었다.
화면에 랜더링 되는 공통 컴포넌트 Header.js 파일에서
react router 패키지 함수인 useLocation()
호출만으로 useEffect와 useState 없이 해당 화면을 랜더링 시키는 현상을 발견했다.
의도한 대로 작동은 한다.....
하지만 useLocation()을 주석 처리하고 실행을 하면 아래 영상처럼 헤더 영역의 로그인 버튼이 프로필 아이콘으로 바뀌지 않는다.
const Header = () => {
// 수정 전
const location = useLocation(); // location이 쓰이지 않음
const [logged, setLogged] = useState(false);
const userData = loadLocalStorage(LOGGEDIN_USER);
useEffect(() => {
userData ? setLogged(true) : setLogged(false);
}, [userData]);
return (
<Wrapper>
<Link to="/">
<img src={logo} alt="자란다 로고" />
</Link>
{userData ? <LogoutButton /> : <LoginButton />}
</Wrapper>
);
};
위의 코드를 보면 useLocation()의 반환 값을 할당받은 location
이 어디에도 쓰이지 않는걸 볼 수 있다.
또한 location 선언 할당 부분을 주석 처리하고 실행하니 로그인이 됐을 때 사용자 프로필 아이콘으로 출력돼야 하는데 로그인 버튼으로 유지된다.
이러한 문제로 인해 useLocation에 대해 추측을 해보았다.
그 당시에는 use
로 시작하니 당연히 hook으로 생각을 했고 그러니 hook 안에는 useState가 내부적으로 선언 되 있어서 해당 state가 location에 대한 정보를 가진 상태일 거고 state가 변화되면서 이로 인해 리랜더가 발생한다고 생각했다.
// 대충 이런식이지 않을까 추측
const useLocation = () => {
const [location, setLocation] = useState({});
...
useEffect(() => {
...
}, []);
처음에 생각한 방법은 useHistory와 useLocation를 사용하기
history.push('/', {user: loginUser})
와 location.state.user
를 사용해서 로그인 사용자 정보를 넘겨주는 방식으로 구현을 했는데 url이 /admin
일때 useHistory로 넘겨주는 해당 사용자 정보가 없으니 프로필 아이콘이 안 나온다.
그렇다고 해당 url마다 사용자 정보를 넘겨줘야 하는 방법을 쓰자니 비효율적이라 생각했다.
결국 useEffect의 deps
에 location을 추가하여 로컬 스토리지에 있는 로그인된 사용자의 정보를 불러오는 방식으로 수정했다.
그러나 URL에 따른 location이 변화할 때마다 로그인된 사용자의 정보를 계속 로드할 테니 조건문을 추가해서 로그인된 user가 없을 때만 불러오도록 수정했다.
추가 요소 : if(로그인된 사용자 정보가 없으면) 로그인된 사용자 정보를 세팅한다.
...
useEffect(() => {
if(!userData) setUserData(loadLocalStorage(LOGGEDIN_USER)); // 조건 처리
},[location]);
...
하지만 위의 영상에서 볼 수 있듯이 관리자 페이지에서 로그아웃을 하게 되면 메인 페이지로 라우팅이 되는데 프로필 아이콘이 로그인 버튼으로 안 바뀐다.
당연히 안 바뀌는 게 맞다.
왜냐? userData가 바뀌면서 Header 컴포넌트 리랜더를 발생시키는 트리거가 없기 때문!!!
setUserData를 로그아웃 버튼의 props로 전달해줘서 로그아웃 함수 내부에 선언을 해줘서 트리거를 만들어줬다.
// in Header.js
...
useEffect(() => {
if(!userData) setUserData(loadLocalStorage(LOGGEDIN_USER)); // 조건 처리
},[location]);
return (
<Wrapper>
...
{loggedInUser ? <LogoutButton setLoggedInUser={setLoggedInUser} /> : <LoginButton />}
</Wrapper>
);
...
// in LogOutButton.js
const handleLogout = () => {
loggedOutStorage();
setLoggedInUser(null); // 로그인된 사용자 정보 초기화
};
해당 코드 수정은 끝났지만, 여전히 남아있는 의문점!
React Router 공식 홈페이지에서 아무리 찾아봐도 해당 내용을 못 찾다가
결국 깃헙 React Router프로젝트 코드에서 useLoaction이 정의된 코드 부분을 찾았다.
export function useLocation() {
if (__DEV__) {
invariant(
typeof useContext === "function",
"You must use React >= 16.8 in order to use useLocation()"
);
}
return useContext(RouterContext).location;
}
추측했던거 와는 달리 useContext로 만들어져있네????
그래서 찾아봤다...모르니 찾아봐야지 어쩌겠누...
리액트 공식 홈페이지에 어떻게 설명되어 있냐면
컴포넌트에서 가장 가까운 <MyContext.Provider>가 갱신되면 이 Hook은 그 MyContext provider에게 전달된 가장 최신의 context value를 사용하여 렌더러를 트리거 합니다.* 상위 컴포넌트에서 React.memo 또는 shouldComponentUpdate를 사용하더라도 useContext를 사용하고 있는 컴포넌트 자체에서부터 다시 렌더링됩니다.
useContext는 결국 다시 랜더링 된다.
음....생각보다 간단한 문제였다.
이 정보를 찾느라 수많은 탭창을 켜놨었는데 ㅋㅋㅋ 팀원들이 보더니 좀 끄면 안 돼요?? 라고 ㅋㅋㅋㅋ
사실 이 문제는 7ill Resource팀원 모두가 "왜 되지?"를 시전한 문제이다.
이 문제로 배포 전까지 계속 토의하다가 1시간이 넘게 안풀렸었다.
그리고 일요일날 문제 정의를 하기 위해 팀원들을 소환(?)하고 같이 찾아보고 몇시간이 흐르고ㅋㅋㅋㅋ
내가 적은 거 외에 다른 팀원들이 시도한 것도 있는데 할 때마다 뭐가 그리 안되는지 ㅋㅋㅋㅋ
다시 문제 파악하고 해결의 반복 ㅋㅋㅋ
뜬금없는 한줄평 : 역시 같이 공부하는 사람들끼리 얘기하는게 재밌고 배울점이 많다.
공식 홈페이지에서 useContext로 인한 리랜더를 트리거한다.라도 했으니 useContext가 사용되는 ContextAPI를 공부하러가야겠네???
이 코드 짠 담당자로서... "이게 대체 머선일이고..."만 반복했던 저... 어제 함께 대화하면서 현찬님의 탐구력에 붐업👍하고 갑니다...!
그리고 마지막에 "이제 useContext가 사용되는 ContextAPI를 공부하러가야겠네???"라는 말 듣고 소름이 쫙... 공부 어떻게 할 지 모르겠으면 고개를 들어 현찬을 보라...^^!🤓
현찬님 첫 블로그 어제 함께 고민했던 내용이라 너무 재밌고 흥미롭게 봤고! 앞으로도 더 자주 써주시라고요!!!! 블로그 중독 plz🙏 그리고 탭도 수시로 정리 plz🙏