이 피드백을 받고 아차 싶었다. 사실 내 프로젝트의 경우, 그냥 단순하게 이메일과 비밀번호의 유효성 검사 로직 메세지만 노출하면 되기 때문에 중복적으로 나올 부분도 없고 Auth 컴포넌트도 로그인 페이지와 회원가입 페이지에 중복으로 적용되는 UI를 정의한 것이기 때문에 한 곳에서만 유효성 검사 메세지를 보여주면 되니까, 굳이 외부로 빼내거나 상수로 정의할 필요성을 못 느꼈다. 하지만 피드백을 받고 생각해보니까 단순히 중복적으로 나오는 것만이 아니라 외부로 빼내고 상수로 정의한 다음에 그 상수를 자체를 컴포넌트에서 사용하는 것이 좀 더 가독성이 좋고 의미도 확실하게 전달되는 것 같다. (이 경우에도 상수의 네이밍을 어떻게 정하는지가 어려운 것 같다.)
<ValidLabel htmlFor="email">비밀번호는 8자 이상이여야 합니다.</ValidLabel>
<ValidLabel htmlFor="email">{VALID_MESSAGE_PASSWORD}</ValidLabel>
window.location.pathname에 따라 노출하려고 하니, 코드가 너무 길어지고 복잡해지는 것 같았다.
{(window.location.pathname === '/signin' ||
window.location.pathname === '/signup' ||
window.location.pathname === '/todo') && (
<Button
onClick={() => {
navigate('/')
}}
>
<AiFillHome />
</Button>
)}
그래서 공통적인 부분을 가지고 리팩토링 하기 위해서 buttons라는 배열을 만들고 그 안에 여러가지 속성을 담고 있는 객체를 담아서 map으로 해결하는 방식으로 시도했다.
const buttons = [
{ path: '/', text: <AiFillHome />, showOnPaths: ['/signin', '/signup', '/todo']},
{ path: '/signup', text: '회원가입', showOnPaths: ['/', '/signin'] },
{ path: '/signin', text: '로그인', showOnPaths: ['/', '/signup'] },
{ path: '', text: '로그아웃', showOnPaths: ['/', '/todo'] }
]
return (
<Nav>
<h1>Wanted-Pre-Onboarding</h1>
<FlexDiv>
{buttons.map(
(button) =>
button.showOnPaths.includes(currentPath) && (
<Button
key={button.path}
onClick={() => {
if (button.text === '로그아웃') {
handleLogout()
} else {
navigate(button.path)
}
}}
>
{button.text}
</Button>
)
)}
</FlexDiv>
</Nav>
)
이번에 멘토님 피드백으로 useNavigate를 사용할 수 있게 navigate에 변수에 저장한 뒤 그걸 전역에서 사용하고 있었는데, 이 경우 전역에서 관리하는 것보다 각 컴포넌트에서 그냥 useNavigate로 불러오는 것이 더욱 적절할 것 같다고 해서 그 부분에 대해서 고민하다가 결국 단순히 useNavigate를 불러와서 사용하는 건데 굳이 전역으로 관리하지 않는 것이 맞다는 관점으로 이야기해주신 것 같아, 수긍하고 다시 전역에서 navigate를 삭제하고 각 컴포넌트에서 사용할 수 있도록 해주었다. 그 과정에서 useEffect에 accessToken이 있을 때 todo 페이지로 리다이렉션 하는 로직을 넣어서 관리하려고 하는데, useEffect에서 오류가 발생했다.
React Hook "useEffect" is called conditionally.
React Hooks must be called in the exact same order in every component render.
Did you accidentally call a React Hook after an early return?eslintreact-hooks/
rules-of-hooks
처음에는 에러 메세지를 읽고도 제대로 이해하지 못했다가, 구글링하고 나서 이유를 알게 되었다. Hook의 경우 컴포넌트 내부에서 Hook 이전에 조건문에서 return 종료하는 로직이 Hook보다 먼저 나오게 되면 Hook을 사용할 수 없다고 한다. 그전에 Context로 관리하지 않을 때는 아래와 같은 종료 조건문이 없었다.
if (!authContext) {
return null
}
하지만 context로 관리하고 나서 authContext가 없으면 종료하는 조건문을 Hook이전에 넣어두었기 때문에 방금과 같은 오류가 발생했다. 그래서 조건문을 Hook 아래에 배치해서 문제를 해결했다.
그리고 해당 에러는 ESLint 규칙 중의 하나인 "react-hooks/rules-of-hooks" 규칙에 의해 발생하는 것이다.
이 규칙은 React의 Hook들을 정확한 순서로 호출해야 함을 강제하고 있다. 그렇지 않으면 예상치 못한 버그를 발생시키기 때문이라고 한다.
사실 ESLint 규칙 중의 하나이니까 해당 규칙을 off하는 방식으로 문제를 해결할 수도 있었지만, 권장되지 않는 방식인 것 같아, 앞으로는 return 종료하는 조건문 뒤에서 호출하지 않고 앞에서 호출하는 방식을 꼭 기억해두었다가 해당 에러가 발생했을 때 빠르게 해결하도록 하자.