On a couch

[메인프로젝트] 커스텀 훅 컴포넌트에 넣기 본문

코드스테이츠 FE/프로젝트

[메인프로젝트] 커스텀 훅 컴포넌트에 넣기

couch 2022. 10. 1. 20:02

여차저차해서 토큰 검사하는 커스텀훅을 만들었다.

만들고 나서 생각해보니 이것도 hook이라서 컴포넌트 최상위에서만 불러올 수 있고 조건문 아래에서 실행할 수 없다는 것에 멘붕이 왔다.
이전에 만든 useGetCards에서 힌트를 얻어, 인증 과정이 진행중인지 외부에 알려주는 authLoading 상태를 만들었다.

 이 훅을 불러오는 컴포넌트에서는 useEffect에서 authLoading과 그 외 필요한 dependency에 따라 화면을 보여주는 구조로  바꾸었다.

 

https://couch.tistory.com/228

 

[메인 프로젝트] 토큰 검증 커스텀훅 / invalid hook call 에러

문제 상황 난생 처음 token 로그인 기능을 만들며 여기저기 부딪혔다. token 유효성 검사 기능은 여기저기에서 다 쓰이다보니까  TokenAuth.js 라는 파일에 전부 만들어두고 필요한 곳에서 import해서

couch.tistory.com

 

재활용 가능한 커스텀훅을 만들기는 했는데, 컴포넌트마다 각자의 맥락이 또 있다.

마이페이지에서는 useGetCards와 useGetAuth의 실행 순서가 꼬이고, 글쓰기 페이지에서는 useGetAuth와 handleSubmit의 기존 기능이 꼬일 뿐더러 useGetAuth를 여러 번 실행시켜야 한다.

또 여차저차해서 해결은 했는데, 과정이 복잡하다보니 내가 나중에 보고 못 읽을까 봐 글로 적어 놓는다.


팀원 도시님이 만들어 주신 글쓰기 페이지가 있다.

API가 기존에는 post 요청을 보내면 모두 수용했지만, 로그인 기능을 붙이면서 authentication 헤더에 들어있는 access token을 검사하도록 바뀌었다.

그래서 만들어져 있는 글쓰기 페이지에 내가 맡은 로그인 기능을 붙여야 한다.

 

생각한 flow는 이렇다.

 

1. 글쓰기 버튼을 눌렀을 때 우선 로컬에 저장되어 있는 isLogin 변수값을 통해 1차적으로 로그인 상태를 필터링한다.

 

2. 사용자 경험 상 글을 열심히 다 쓰고 나서 글쓰기 버튼을 눌렀는데 '로그인 안 됐으니 돌아가라'라고 뜬다면 화날 것이다.

access token의 유효시간이 얼마 안 남았을 수 있으니 페이지로 넘어왔을 때 무조건 access token을 다시 검사해 access token과 refresh token을 둘 다 갱신한다.

 

3. 기존에는 버튼을 누르면 유효성검사를 한다. 통과하지 못하면 alert창만 뜨고 통과하면 axios를 실행시킨다.

  3-1. 유효성 검사의 마지막 단계에서 useGetAuth를 실행시킬 수도 있겠지만, 나같으면 토큰이 만료된 줄도 모르고 요구하는 거 전부 다 적었는데 마지막에서야 만료됐다고 알려주면 또 화날 것 같다. 그래서 헛수고 덜 하도록 각 유효성 검사 단계마다 useGetAuth 실행을 넣고 싶다.

  3-2.  useGetAuth는 일반 함수처럼 필요할 때마다 호출할 수 없다 'ㅅ' ;;;;;;; 그래서 컴포넌트에 tryAuth 라는 상태를 만들고 useGetAuth 내부의 useEffect 의존성배열에 전달했다. 이 값이 바뀔 때마다 훅이 실행되어 authLoading과 authCheck값이 변경된다.

  3-3. 이미 처음 마운팅될 때 useGetAuth는 무조건 실행되므로 AuthLoading이 false이다. 여기서는 여러번 검사해야 하므로 useGetAuth에서 authLoading이 false인 상태라면 검사 전 true로 변경한다.

  3-4. 컴포넌트의 useEffect에서는 authLoading 값과 authCheck 값에 따라 실제 axios 실행 여부를 결정한다.

 

// 새로운 마일스톤을 작성하는(글쓰기 페이지) 컴포넌트
export const NewMilestone = () => {
  const navigate = useNavigate()
  const [tryAuth, setTryAuth] = useState(false)
  const { authLoading, authCheck } = useGetAuth(trySubmit)
  const [category, setCategory] = useState('')
  const [title, setTitle] = useState('')
  const [description, setDescription] = useState('')
  const [successAward, setSuccessAward] = useState('')
  const [failurePenalty, setFailurePenalty] = useState('')
  const [endDate, setEndDate] = useState('') //종료날짜 받을 곳
  const [milestoneImageId, setMilestoneImageId] = useState() // 마일스톤 베너 이미지 아이디 받을 곳
  const [imgName, setImgName] = useState('') // 이미지 이름 받을 곳
  const [imgBase, setImgBase] = useState([]) // 미리보기 이미지 데이터를 받을 곳
  const [imgFile, setImgFile] = useState(null) //파일을 받을 곳
  const [openChoseImage, setOpenChoseImage] = useState(false) //이미지 아이콘 클릭 시 상태 관리
  
  /* ... 중략 ... */
  
  // 전송 버튼 클릭 시 tryAuth 상태 true로 변경
  // => useGetAuth가 tryAuth 상태에 의존하므로 실행되어 토큰 상태 재확인
  // => useEffect에서 tryAuth 상태가 true일 경우 submit 실행
  const handleClickSubmit = () => {
    console.log('생성하기 버튼 클릭')
    setTrySubmit(true)
  }

  const submit = () => {
    // 게시글 작성에 필요한 각 단계가 완성되지 않았을 경우 tryAuth을 false로 변경
     if (category === '' || category === '선택안함') {
      setTryAuth(false)
      alert('카테고리를 입력하세요')
    } else if (title === '') {
      setTryAuth(false)
      alert('제목을 입력하세요')
    } else if (description === '') {
      setTryAuth(false)
      alert('목표를 소개하는 글을 입력하세요')
    } else if (successAward === '') {
      setTryAuth(false)
      alert('성공 시 나와의 약속을 만들어주세요')
    } else if (failurePenalty === '') {
      setTryAuth(false)
      alert('실패 시 나와의 약속을 만들어주세요')
    } else if (endDate === '') {
      setTryAuth(false)
      alert('종료 날짜를 선택해주세요')
    } else {
      if (milestoneImageId === undefined && imgName) {
        setTryAuth(false)
        alert('업로드하지 않은 이미지가 존재합니다. 이미지 업로드를 하세요.')
      } else {
        setMilestoneImageId('')
        try {
          axios({
            method: 'post',
            url: '/v1/goal/create',
            data: {
              title: title,
              description: description,
              successAward: successAward,
              failurePenalty: failurePenalty,
              endDate: endDate,
              category: category,
              imageId: milestoneImageId,
            },
          }).then((res) => {
            console.log(res)
            if (res.status === 201) {
              navigate(`/goal/detail/${res.data}`)
            }
          })
        } catch (err) {
          console.log(err)
        }
      }
    }
  }

  useEffect(() => {
    // 아직 auth 검사가 진행중이라면 스탑
    if (authLoading === true) {
      null
    }
    // auth 검사를 하고 보니 토큰이 만료되었다면 스탑
    else if (authCheck === false) {
      console.log('authcheck: ', authCheck)
      alert('장기간 이용하지 않아 로그아웃 되었습니다')
      navigate('/login')
    }
    // 토큰이 살아있다면 submit 함수 실행
    else if (trySubmit === true) {
      try {
        submit()
      } catch (err) {
        console.log(err)
        alert('오류가 발생했습니다')
        navigate(0)
      }
    }
  }, [authLoading])