[React] Trouble Shooting(MBTI-app)

우지끈·2024년 11월 27일
post-thumbnail

문제 1.

로그인 후 프로필 페이지로 이동했을 때, 새로 고침 전에는 화면을 불러오지 못하는 문제가 있었다. 콘솔에서 에러메세지를 살펴보니 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의 값이 localStorageRedux 상태에서 다르게 처리되고 있었던 것이다.

dispatch(updateUserInfo(userProfile)); 에서 userProfile 전체 객체 자체를 전달했기에 Redux에는 객체가 제대로 저장되었으나, localStorage에는 객체가 문자열로 변환되지 않은 상태로 저장되어 h2에서 userNickname을 불러오지 못하게 된 것이다.

따라서 처음 로그인했을 때, userNicknamelocalStorage에 저장되었고, 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에도 동일하게 문자열을 할당하여 초기 상태를 일관되게 설정해주면...! 문제가 해결될 것이다. 이번 스터블슈팅을 통해 상태 일관성 유지의 중요성과 타입 관리가 어떻게 예상치 못한 오류를 방지할 수 있는지를 다시 한 번 깨달을 수 있었다. 앞으로도 이러한 기본 원칙을 염두에 두고 안정적인 코드를 작성해 나가야겠다.


문제 2.

두 번째 문제는 여러 화면들에서 스크롤이 생기는걸 원치 않았는데 스크롤이 생성되는 문제였다. 이 문제의 경우, 가장 부모 요소에 height: 100vh를 줬어야 했는데 그 자식 요소에 height: 100vh를 줘서 overflow로 인해 자동으로 스크롤이 생기게 된 것이었다.

이번 문제를 통해 CSS에서 부모-자식 관계와 레이아웃 설정의 중요성을 다시금 깨달을 수 있었다. 작은 실수 하나로도 예상치 못한 결과를 초래할 수 있음을 느꼈고, 앞으로는 레이아웃 구조를 더 꼼꼼히 살피고 실수를 줄이기 위해 지속적으로 공부해야겠다는 다짐을 하게 되었다.

0개의 댓글