React Hook Form을 사용해 로그인 폼을 구성했지만, onSubmit과 onClick을 직접 처리하여 loginUser API를 호출하고 accessToken을 localStorage에 저장하려 했다.
내가 생각하기엔 문제는 없어 보였는데 로그인 페이지가 렌더링 됨과 동시에 onClick안의 함수와 토큰 값을 출력하는 콘솔이 출력되는 문제가 발생했다.
token=null
token=null
onClickFunction {}
onClickFunction {}
콘솔이 이런 식으로 나와 당황스러웠다.
우선, 같은 콘솔 로그가 두 번 출력되는 이유는 React의 Strict Mode 때문인 것 같아 Strict Mode를 비활성화했다.
그럼 두 번째. 토큰은 왜 null이 나올까?
코드를 보면서 설명해보려고 한다.
우선 apollo setting 파일이다.
// apollo-setting.tsx
export default function ApolloSetting(props: IApolloSetting) {
const token =
typeof window !== "undefined" ? localStorage.getItem("accessToken") : "";
console.log(`token: ${token}`);
const uploadLink = createUploadLink({
uri: "http://main-practice.codebootcamp.co.kr/graphql",
headers: {
Authorization: token ? `Bearer ${token}` : "",
},
});
(생략)
}
typeof window !== "undefined"를 사용하여 코드 실행 시점이 클라이언트인지 서버인지를 구분했다.
서버 사이드에서는 window 객체가 존재하지 않기 때문에 localStorage에 접근할 수 없기 때문이다.
헤더에서도 토큰이 없는 경우 빈 문자열을 보내는 이유는 백엔드 로직 상 공란이 들어오면 에러를 던져주기 때문이다.
accessToken을 꺼내서 사용하는 쪽 코드를 봤으니 이제 accessToken을 넣어주는 쪽도 확인해 볼 필요가 있다.
아래 코드는 React-hook-form을 사용해서 작성된 코드이나 지금의 중요는 해당 부분이 아니라 넘어가겠다.
아래 코드가 문제가 발생한 코드이다.
// loginFormComponents
const USER_LOGIN = gql`
mutation loginUser($email: email, $password: password) {
loginUser(email: $email, password: $password) {
accessToken
}
}
`;
const LoginFromComponent = () => {
const router = useRouter();
const { register, handleSubmit } = useForm();
const [loginUser] = useMutation(USER_LOGIN);
const onClickSubmit = async (data) => {
const result = await loginUser({
variables: {
email: data.email,
password: data.password,
},
});
console.log(`result: ${JSON.stringify(result)}`);
localStorage.setItem("accessToken", result.data.accessToken);
router.push("/");
};
(tsx 생략)
};
위 코드에서 문제는 2가지가 있다.
1. graphQL api를 전송할 때 주의 할 부분인 변수의 타입 부분이 잘못되었다.
2. result의 데이터를 찍어본 후 알게 된 객체 내부 구조
// loginFormComponent
const USER_LOGIN = gql`
// 타입부분은 API 명세와 동일하게 String!으로
mutation loginUser($email: String!, $password: String!) {
loginUser(email: $email, password: $password) {
accessToken
}
}
`;
const LoginFromComponent = () => {
const router = useRouter();
const { register, handleSubmit } = useForm();
const [loginUser] = useMutation(USER_LOGIN);
const onClickSubmit = async (data) => {
const result = await loginUser({
variables: {
email: data.email,
password: data.password,
},
});
console.log(`result: ${JSON.stringify(result)}`);
// ! 콘솔에 제대로 찍어보니 loginUser 안에 accessToken 존재. => 기존에 token 값 null 문제 해결
localStorage.setItem("accessToken", result.data.loginUser.accessToken);
router.push("/");
};
(tsx 생략)
};
console.log(`result: ${JSON.stringify(result)}`)의 결과
result: {
"data":{
"loginUser":{
"accessToken":"...","__typename":"Token"
}
}
}
이 결과를 바탕으로 localStorage.setItem("accessToken", result.data.loginUser.accessToken)을 호출하여 정상적으로 accessToken 값을 저장할 수 있었다.
앞으로 위와 같은 문제가 발생하면
1. React Strict Mode가 활성화되어 있는지 확인하고,
2. 콘솔에서 response 객체의 구조를 꼼꼼히 확인한 후 문제를 해결해야겠다고 생각했다.
'💻 Project > [개인] Tripper' 카테고리의 다른 글
여행자들의 공간, Tripper 프로젝트 시작 (수정 중) (0) | 2024.11.06 |
---|