로그인 후 프로필 페이지로 이동했을 때, 새로 고침 전에는 화면을 불러오지 못하는 문제가 있었다. 콘솔에서 에러메세지를 살펴보니 Profile.jsx 파일의 <h2>에서 에러가 발생했다 하여 해당 부분을 확인해보았다.
// Profile.jsx
return (
<div className="bg-[#A5FF5E] h-lvh flex justify-center">
<div className="border border-black bg-white w-1/3 mt-20 h-56 flex flex-col items-center py-4">
<h1 className="text-2xl font-semibold">프로필 수정</h1>
// 에러 발생
<h2 className="mt-2">{user}님 환영합니다!</h2>
<form onSubmit={handleSubmit} className="flex flex-col items-center">
<div className="mt-7 flex gap-5">
<label>닉네임</label>
<input
value={inputValue}
onChange={handleInputChange}
placeholder="변경할 닉네임을 입력해주세요."
className="w-80 border border-black px-2 placeholder:text-s"
/>
</div>
<button
type="submit"
className="mt-8 text-sm border border-black px-5 py-1 rounded-full bg-black text-white font-semibold transform hover:scale-105 hover:shadow-lg hover:bg-black-900 transition-all duration-300 ease-in-out"
>
프로필 업데이트
</button>
</form>
</div>
</div>
);
};
<h2>에서는 user를 통해 현재 접속한 user의 닉네임을 띄워주고자 했는데 user를 제대로 읽어오지 못하고 있는 것 같아 관련된 코드를 하나씩 살펴보았다.
// Profile.jsx
const Profile = () => {
const [inputValue, setInputValue] = useState("");
const user = useSelector((state) => state.auth.userNickname);
const dispatch = useDispatch();
const handleInputChange = (e) => {
setInputValue(e.target.value);
};
코드를 살펴보니 user는 useSelector를 통해 구독하고 있는 상태였고, 해당 상태를 확인하기 위해 상태를 정의해둔 authSlice로 거슬러 올라갔다.
// authSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
isLogin: !!localStorage.getItem("accessToken"),
userNickname: localStorage.getItem("userNickname") ?? "",
};
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
changeLogin: (state) => {
state.isLogin = true;
},
changeLogout: (state) => {
state.isLogin = false;
localStorage.clear();
},
updateUserInfo: (state, action) => {
const newUserInfo = action.payload;
state.userNickname = newUserInfo;
},
},
});
export const { changeLogin, changeLogout, updateUserInfo } = authSlice.actions;
export default authSlice.reducer;
초기값 부분을 살펴보면, 현재 userNickname은 문자열로 정의되어 있고 updateUserInfo 리듀서를 통해 action.payload를 할당해주고 있다.
따라서 적합한 값을 할당해주고 있는지 확인해보았다.
// Login.jsx
const Login = () => {
const dispatch = useDispatch();
const handleLogin = async (formData) => {
try {
const response = await login(formData);
if (response.success) {
toast.success("로그인 성공!");
localStorage.setItem("accessToken", response.accessToken);
// 로그인 상태 변경
dispatch(changeLogin());
const userProfile = await getUserProfile(response.accessToken);
// 문자열 할당
localStorage.setItem("userNickname", userProfile.nickname);
// 객체 할당
dispatch(updateUserInfo(userProfile));
}
} catch (error) {
toast.error("로그인에 실패했습니다. 다시 시도해주세요.");
console.error(error);
}
};
여기서 문제를 발견했는데 문자열을 할당해줘야 하는 부분에 객체를 할당하고 있었다... userNickname의 값이 localStorage와 Redux 상태에서 다르게 처리되고 있었던 것이다.
dispatch(updateUserInfo(userProfile)); 에서 userProfile 전체 객체 자체를 전달했기에 Redux에는 객체가 제대로 저장되었으나, localStorage에는 객체가 문자열로 변환되지 않은 상태로 저장되어 h2에서 userNickname을 불러오지 못하게 된 것이다.
따라서 처음 로그인했을 때, userNickname이 localStorage에 저장되었고, Redux에도 저장되었지만, 페이지가 처음 렌더링 될 때는 둘의 상태가 동기화되지 않아 정확한 값을 읽어올 수 없어 에러가 발생하며 아무것도 렌더링되지 않았던 것이다. 그 뒤 새로고침을 하면 Redux가 로컬스토리지로부터 다시 초기값을 받아오면서 그제서야 화면이 정상적으로 렌더링 됐던 것이다.
const Login = () => {
const dispatch = useDispatch();
const handleLogin = async (formData) => {
try {
const response = await login(formData);
if (response.success) {
toast.success("로그인 성공!");
localStorage.setItem("accessToken", response.accessToken);
// 로그인 상태 변경
dispatch(changeLogin());
// 유저 정보 가져오기
const userProfile = await getUserProfile(response.accessToken);
localStorage.setItem("userNickname", userProfile.nickname);
// 수정
dispatch(updateUserInfo(userProfile.nickname));
}
} catch (error) {
toast.error("로그인에 실패했습니다. 다시 시도해주세요.");
console.error(error);
}
};
따라서 Redux에도 동일하게 문자열을 할당하여 초기 상태를 일관되게 설정해주면...! 문제가 해결될 것이다. 이번 스터블슈팅을 통해 상태 일관성 유지의 중요성과 타입 관리가 어떻게 예상치 못한 오류를 방지할 수 있는지를 다시 한 번 깨달을 수 있었다. 앞으로도 이러한 기본 원칙을 염두에 두고 안정적인 코드를 작성해 나가야겠다.
두 번째 문제는 여러 화면들에서 스크롤이 생기는걸 원치 않았는데 스크롤이 생성되는 문제였다. 이 문제의 경우, 가장 부모 요소에 height: 100vh를 줬어야 했는데 그 자식 요소에 height: 100vh를 줘서 overflow로 인해 자동으로 스크롤이 생기게 된 것이었다.
이번 문제를 통해 CSS에서 부모-자식 관계와 레이아웃 설정의 중요성을 다시금 깨달을 수 있었다. 작은 실수 하나로도 예상치 못한 결과를 초래할 수 있음을 느꼈고, 앞으로는 레이아웃 구조를 더 꼼꼼히 살피고 실수를 줄이기 위해 지속적으로 공부해야겠다는 다짐을 하게 되었다.