[코딩온] 프로젝트 조각 회고록 : 7~10일차

Yunhye Park·2024년 1월 5일
0
post-custom-banner

1/1(월)

오늘로 회원가입/로그인 마지막(과연...) 예외처리를 해줬다.

  • 에러 메시지 보여주고 폼 필드 수정 시 메시지 사라지게

지금까지 useForm을 사용해 구현한 기능을 정리하자면 이렇다.

회원가입 시

  1. 각 필드 상황별 유효성 검사
  2. 유효성 검사 통과 후 회원가입 버튼 동작
    • 아이디, 닉네임 모두 중복 통과 못하면 가입 불가 + 안내 메시지
  3. 엔터키 동작
  4. 에러 메시지 보여주고 폼 필드 수정 시 메시지 사라지게

로그인 시

  1. 하나라도 빈값이면 버튼 disabled
  2. 불일치 안내 메시지
  3. 엔터키 동작
  4. 에러 메시지 보여주고 폼 필드 수정 시 메시지 사라지게

한 것

  • useCallback 활용한 최적화
  • 회원가입/로그인 각각 input, button 컴포넌트화

이전까지는 예외처리에 집중했다면, 오늘은 컴포넌트하며 정리하기!

배움

useMemo 사용기

리액트에서 함수는 함수 내부에 있는 props나 state가 변경될 때마다 리렌더링 된다. 불필요한 리렌더링이 발생하지 않도록 id, pw, nickname 값을 추적하는 함수에 useMemo를 적용했다.

  1. 기존 코드 :
  const idValue = watch('id') || '';
  const pwValue = watch('pw') || '';
  const nicknameValue = watch('nickname') || '';

watch는 useForm에서 제공하는 함수 객체이다. 폼 필드의 'name'으로 해당 필드의 값을 실시간 추적한다.

  1. 변경 코드 :
  const watchObj = watch();

// 닉네임 중복 확인을 위한 값 추적
  const nicknameValue = useMemo(
    () => watchObj.nickname || '',
    [watchObj.nickname]
  );

  // 로그인 시 빈값 확인
  useEffect(() => {
    const isIdValid = (watchObj.id || '').trim() !== '';
    const isPwValid = (watchObj.pw || '').trim() !== '';
    const isSignInValid = isIdValid && isPwValid;

    setSignInCk(isSignInValid);
  }, [watchObj.id, watchObj.pw]);
  • trim 메서드는 문자열 메서드라서 undefined를 방지하고자 watchObj.id || ''로 작성했다.

  • nickname은 회원가입 시 중복 확인을 위한 기능이라 그 '값'만 중요해서 useMemo를, id와 pw는 현재 값에 따라 state를 업데이트 해야 해서 useEffect를 사용했다.

트러블 슈팅

useCallback 디펜던시에 무엇을?

  1. 발생 : useCallback 디펜던시에 state를 넣으면 동작이 안 되고 setState를 넣어야 동작
  2. 원인 : state는 비동기 처리한다!
    setState 함수를 의존성 배열에 넣지 않으면 항상 이전의 값을 참조해서 예상치 못한 동작이 발생할 수 있다.
  3. 해결 :
  const handleCheck = useCallback(
    async (type, value) => {
      try {
        if (errors.id) {
          setMsg((prev) => ({
            ...prev,
            [`${type}Duplicate`]: '❌',
          }));
        } else {
          const data = { [type]: value };
          const response = await axios.post(
            'http://localhost:8000/member/checkDuplicate',
            data
          );

          if (response.data.result) {
            setSignUpCk((prev) => ({ ...prev, [type]: true }));
            setMsg((prev) => ({
              ...prev,
              [`${type}Duplicate`]: 'OK',
            }));
          } else {
            setSignUpCk((prev) => ({ ...prev, [type]: false }));
            setMsg((prev) => ({
              ...prev,
              [`${type}Duplicate`]: '❌',
            }));
          }
        }
      } catch (err) {
        console.error('중복 체크 에러: ', err);
      }
    },
    [setSignUpCk, setMsg, errors.id]
  );

소감

  • useMemo와 useCallback은 디펜던시에 어떤 값을 넣어줘야 할지가 여전히 헷갈린다. 그래도 맨처음 이론 배웠을 때보단 수확이 있다.

    • 1) 함수 내부에 state나 props가 있을 때 불필요한 렌더링이 있는지 생각.
    • 2) state는 비동기 함수이므로, setStatestate를 바꿀지라도 순서 처리 때문에 적용이 안 될 수 있다.
  • 처음에 컴포넌트 구성 짜는 게 중요한 것 같다. input이 많으면 이걸 컴포넌트화할 수 있단 걸 뒤늦게 떠올려서 나눴는데 정말 대대적인 작업이었다. 💦


1/2(화)

한 것

  • css 디자인
  • redux 설정
  • redux-persist로 로그인 정보 저장

트러블 슈팅

redux는 직렬화된 데이터를 권장한다

  1. 발생 : redux 설정 후 redux-persist 라이브러리로 변경 적용
  2. 오류 메시지 :
serializableStateInvariantMiddleware.ts:194
A non-serializable value was detected in an action,
in the path: register Value: ƒ register(key)
  1. 의미 : 액션 함수에 직렬화 되지 않은 값이 있다.

직렬화?

리덕스는 상태 관리 라이브러리이다. 상태를 잘 관리한다는 건 뭘까? 상태를 저장하거나 전송할 때 상태의 일관성을 유지하고 다양한 환경에서 사용할 수 있게 한다. 이를 위해 직렬화(serialize)를 권장한다.

redux-persist를 사용하면서 오류 메시지가 발생했다는 것에 주목해보자. 이 라이브러리는 사용자가 페이지를 새로고침해도 로그인 상황을 클라이언트 측에서 유지하고자 할 때, 즉 리덕스 상태를 로컬 스토리지나 세션 스토리지에 저장하고 싶을 때 유용하다.

직렬화 관련 에러 메시지가 콘솔에 떠도 동작에 문제가 생기는 건 아니다. 다만 Redux DevTools로 더이상 상태를 추적할 수 없다.

해결 방법으로는 액션에 toString 메서드를 사용하거나 미들웨어를 걸어주는 방법이 있다. 둘 중 커스터마이징하기 편한 미들웨어 추가를 택했다.

  1. 해결 :
const store = configureStore({
  reducer: {
    auth: persistedAuthReducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: false,
    }),
});

1/3(수)

한 것

  • scss 미디어 쿼리 적용
  • 로그인 여부로 페이지 접근 다르게
  • 모달 레이아웃

트러블 슈팅

  1. 발생 : 로그인 없이 마이페이지 접속 시 로그인 화면 보여주기 (useNavigate)
  2. 오류 메시지 :
You should call navigate() in a React.useEffect(),
not when your component is first rendered.
  1. 의미 : useNavigate로 라우터 변경 시, 컴포넌트가 처음 렌더링될 때가 아니라 useEffect 안에서 호출하라
  2. 해결 :
  useEffect(() => {
    if (!memberId) navigate('/member/signin');
  }, [memberId]);

1/4(목)

한 것

  • 회원 탈퇴
    • 모달 창 -> 확인 클릭 후 메인 이동
  • 회원정보 수정 페이지
    • UI 수정 (섹션 별 버튼)
    • 닉네임, 이메일 값 불러오기
  • ProductCard 컴포 설정 가져와서 적용

배움

재사용할 컴포넌트는 미리 구분해서 정하기

내가 맡은 마이페이지는 여러 목록을 보여주어야 한다. 찜 목록, 판매글 목록, 작성한 리뷰 목록, 채팅 목록까지. UI를 새로 만들 필요는 없고, 다른 팀원이 이미 만들어둔 컴포넌트를 CSS 설정과 함께 가져온다.

다른 팀원 분은 한 곳에서만 쓰일 것이라 예상하셨던 것 같다. 따로 컴포넌트 파일을 만들지 않고 한 페이지 안에 같이 두면서 css 설정을 비롯한 여러 요소들이 엉켜있었다.

컴포넌트 자체는 export로 가져올 수 있었지만, css 설정은 복사해와서 고쳐야했다. 컬러감 고르는 디자인적인 부분은 그다지 중요한 대화 주제가 아닌 듯하고, 프론트는 컴포넌트 분리와 기본 설정/세팅 이야기를 잘 나누는 게 중요한 것 같다.

트러블 슈팅

redux에서 설정한 값과 함수 모두 가져올 땐

  1. 발생 : redux action 함수가 사용 안 됨
  2. 오류 메시지 :
TypeError: deleteSuccess is not a function at deleteUser
  1. 원인 : state값과 액션 함수 자체를 가져오므로 connect 필요
  2. 해결 :
const ConnectedMyPageUpdate = connect(
  (state) => ({    
    user: state.auth,
  }),
  { deleteSuccess })(MyPageUpdate);

export default ConnectedMyPageUpdate;

실시간 변경되는 value엔 초기값을

  1. 발생 : 데이터바인딩한 input에 실시간 value 추적(watch) 하려는 중
  2. 경고 메시지 :
Warning: 
A component is changing a controlled input to be uncontrolled. 
This is likely caused by the value changing
from a defined to undefined,  which should not happen. 
Decide between using a controlled or uncontrolled input element 
for the lifetime of the component.
  1. 의미 : 컴포넌트를 제어(value 있음)된 상태에서 비제어된 상태(value가 undefined 등 없음)로 변경하려고 한다.
  2. 해결 : 초기값 빈문자열로 설정
value={watchObj?.nickname || nickname || ''}

세션 쓸 때부턴 로그인 유지 확인

  1. 발생 : 서버에 axios 요청 중 500 에러와 404 에러 번갈아 발생
  2. 원인 :
    1) 404 에러 - 라우터 favoritefavourite으로
    2) 500 에러 - 로그인 풀려서 세션 undefined
  3. 해결 : 원인 파악 후 수정

  1. 발생 : 버튼 클릭 후 모달 창 나와야 하는데 2번 클릭해야 동작
  2. 확인 : 토글은 문제 없음
  3. 원인 : 분리된 컴포넌트라서 서로 다른 토글로 동작
  4. 해결 :
const [deleteToggle, onDeleteToggle] = useToggle(false);

...

{deleteToggle && (
  <ModalBasic
  content="정말 탈퇴하시겠습니까?"
  onButtonClick={deleteUser}
  toggleState={true}
  setToggleState={onDeleteToggle}
/>
  )}

모달에서 토글이 필요한 이유는 창 닫기를 위함이다. 즉 언제나 토글 on 상태여야 해서 props로 true를 넘겨주어 함께 동작하도록 변경했다.

물론 props로 넘기지 않고 기본값을 설정해도 되지만, 어차피 상위 컴포넌트에서 쓰이는 토글 함수를 넘겨주어서(setToggleState={onDeleteToggle}) 형식을 맞춰줄 겸 함께 넘겼다.

화면 사진

✔️ 마이페이지
남은 작업

  • 각 list 눌렀을 때 나오는 데이터 바인딩
    ㄴ 컴포넌트화만 잘 되어있으면 너무 쉬웠을 건데 재사용을 고려하지 않은 컴포넌트(게다가 타인이 작성한)를 가져다가 고쳐 쓰려니 극악의 난이도. 🤦‍♀️
  • 프로필 사진
  • 렌더링 시 찜 목록 나오게 만들고, 선택된 리스트는 텍스트 컬러 번경

✔️ 회원정보 수정

  • 닉네임 input 다 지우면 다시 value 채워지는 에러 고치기
  • 닉네임 변경, 이메일 변경 각각.. 처리. 근데 이건 백에서 준 걸 받아야 할 수 있음.
  • 비밀번호 변경도 마찬가지.
  • 결제정보 서버랑 데이터 주고받기

소감

  • 슬슬 숨가쁘게 적기 시작한다.. 분명 저번보다 플젝 진행 속도가 빠르다고 생각했는데 막상 해보니 잘 모르겠네.
profile
일단 해보는 편
post-custom-banner

0개의 댓글