TIL_2023_08_07

이종현·2023년 8월 8일
0

Today_I_Learned

목록 보기
75/145
post-thumbnail

Today 요약

  1. 멘토링 피드백 답변하기
  2. Hook 선언 전에 조건문을 통해 return 종료되면 안됨

1. What I did?

1.1 멘토링 피드백 답변하기

  • 아무 생각없이 짓는 네이밍 주의하기
    • styled-components로 스타일링한 LI, UL 스타일 컴포넌트?를 Li, Ul로 변경
    • iTodo 인터페이스도 ITodo로 소문자 i를 대문자 I로 변경
      • Todo라는 클래스가 존재할 수도 있기 때문에 인터페이스는 클래스와 구분 짓기 위해 I라는 Interface의 축약 글자를 붙혀서 보통 일반적으로 ITodo라고 명명하는 것이 좋다.
      • 하지만 이 또한 개발자마다 생각이 다른 듯 하다. 현재 나는 조금 애매한 관점을 가지고 있는데, 한 프로젝트 안에서 I를 사용하기도 하고 안하기도 하면 어떨까 하는 생각이 있다. 사실 이번 프로젝트에서 I를 명시하지 않고 인터페이스 네이밍을 해보려고 했으나, 마땅한 게 떠오르지 않아 I를 붙히게 된건데 만약 네이밍이 마땅하지 않으면 I를 붙히고 적당한 네이밍이 있으면 I를 붙히지 않는 걸로 인터페이스 네이밍을 결정하는 건 어떨까하고 생각했었다. 그런데 이는 또한 한 프로젝트 안에서 통일성이 없을 것 같기 때문에 별로 좋지 않은 방법이라 생각한다. 앞으로 프로젝트를 진행하면서 이 부분에 대한 확실한 나만의 관점을 찾아보자.
  • 유효성 검사 로직 메세지 외부에서 공급받을 수 있도록 변경
    • 이 피드백을 받고 아차 싶었다. 사실 내 프로젝트의 경우, 그냥 단순하게 이메일과 비밀번호의 유효성 검사 로직 메세지만 노출하면 되기 때문에 중복적으로 나올 부분도 없고 Auth 컴포넌트도 로그인 페이지와 회원가입 페이지에 중복으로 적용되는 UI를 정의한 것이기 때문에 한 곳에서만 유효성 검사 메세지를 보여주면 되니까, 굳이 외부로 빼내거나 상수로 정의할 필요성을 못 느꼈다. 하지만 피드백을 받고 생각해보니까 단순히 중복적으로 나오는 것만이 아니라 외부로 빼내고 상수로 정의한 다음에 그 상수를 자체를 컴포넌트에서 사용하는 것이 좀 더 가독성이 좋고 의미도 확실하게 전달되는 것 같다. (이 경우에도 상수의 네이밍을 어떻게 정하는지가 어려운 것 같다.)

      <ValidLabel htmlFor="email">비밀번호는 8자 이상이여야 합니다.</ValidLabel>
      <ValidLabel htmlFor="email">{VALID_MESSAGE_PASSWORD}</ValidLabel>
  • 기존에 원하는 페이지에서 원하는 버튼만 호출해서 UI에 표시하는 방법을 변수를 통해 boolean값으로 해결했었는데, url 주소에 따라 표시할 수 있도록 변경했다.
    • 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>
      )

2. What I Learned?

2.1 Hook 선언 전에 조건문을 통해 return 종료되면 안됨

이번에 멘토님 피드백으로 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 종료하는 조건문 뒤에서 호출하지 않고 앞에서 호출하는 방식을 꼭 기억해두었다가 해당 에러가 발생했을 때 빠르게 해결하도록 하자.


profile
데이터리터러시를 중요하게 생각하는 프론트엔드 개발자

0개의 댓글