오늘로 회원가입/로그인 마지막(과연...) 예외처리를 해줬다.
지금까지 useForm을 사용해 구현한 기능을 정리하자면 이렇다.
회원가입 시
로그인 시
이전까지는 예외처리에 집중했다면, 오늘은 컴포넌트하며 정리하기!
리액트에서 함수는 함수 내부에 있는 props나 state가 변경될 때마다 리렌더링 된다. 불필요한 리렌더링이 발생하지 않도록 id, pw, nickname 값을 추적하는 함수에 useMemo
를 적용했다.
const idValue = watch('id') || '';
const pwValue = watch('pw') || '';
const nicknameValue = watch('nickname') || '';
watch
는 useForm에서 제공하는 함수 객체이다. 폼 필드의 'name'으로 해당 필드의 값을 실시간 추적한다.
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를 사용했다.
state
를 넣으면 동작이 안 되고 setState
를 넣어야 동작state
는 비동기 처리한다!setState
함수를 의존성 배열에 넣지 않으면 항상 이전의 값을 참조해서 예상치 못한 동작이 발생할 수 있다. 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은 디펜던시에 어떤 값을 넣어줘야 할지가 여전히 헷갈린다. 그래도 맨처음 이론 배웠을 때보단 수확이 있다.
setState
가 state
를 바꿀지라도 순서 처리 때문에 적용이 안 될 수 있다.처음에 컴포넌트 구성 짜는 게 중요한 것 같다. input이 많으면 이걸 컴포넌트화할 수 있단 걸 뒤늦게 떠올려서 나눴는데 정말 대대적인 작업이었다. 💦
serializableStateInvariantMiddleware.ts:194
A non-serializable value was detected in an action,
in the path: register Value: ƒ register(key)
리덕스는 상태 관리 라이브러리이다. 상태를 잘 관리한다는 건 뭘까? 상태를 저장하거나 전송할 때 상태의 일관성을 유지하고 다양한 환경에서 사용할 수 있게 한다. 이를 위해 직렬화(serialize)를 권장한다.
redux-persist를 사용하면서 오류 메시지가 발생했다는 것에 주목해보자. 이 라이브러리는 사용자가 페이지를 새로고침해도 로그인 상황을 클라이언트 측에서 유지하고자 할 때, 즉 리덕스 상태를 로컬 스토리지나 세션 스토리지에 저장하고 싶을 때 유용하다.
직렬화 관련 에러 메시지가 콘솔에 떠도 동작에 문제가 생기는 건 아니다. 다만 Redux DevTools로 더이상 상태를 추적할 수 없다.
해결 방법으로는 액션에 toString 메서드를 사용하거나 미들웨어를 걸어주는 방법이 있다. 둘 중 커스터마이징하기 편한 미들웨어 추가를 택했다.
const store = configureStore({
reducer: {
auth: persistedAuthReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
You should call navigate() in a React.useEffect(),
not when your component is first rendered.
useEffect(() => {
if (!memberId) navigate('/member/signin');
}, [memberId]);
내가 맡은 마이페이지는 여러 목록을 보여주어야 한다. 찜 목록, 판매글 목록, 작성한 리뷰 목록, 채팅 목록까지. UI를 새로 만들 필요는 없고, 다른 팀원이 이미 만들어둔 컴포넌트를 CSS 설정과 함께 가져온다.
다른 팀원 분은 한 곳에서만 쓰일 것이라 예상하셨던 것 같다. 따로 컴포넌트 파일을 만들지 않고 한 페이지 안에 같이 두면서 css 설정을 비롯한 여러 요소들이 엉켜있었다.
컴포넌트 자체는 export로 가져올 수 있었지만, css 설정은 복사해와서 고쳐야했다. 컬러감 고르는 디자인적인 부분은 그다지 중요한 대화 주제가 아닌 듯하고, 프론트는 컴포넌트 분리와 기본 설정/세팅 이야기를 잘 나누는 게 중요한 것 같다.
TypeError: deleteSuccess is not a function at deleteUser
const ConnectedMyPageUpdate = connect(
(state) => ({
user: state.auth,
}),
{ deleteSuccess })(MyPageUpdate);
export default ConnectedMyPageUpdate;
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.
value={watchObj?.nickname || nickname || ''}
favorite
을 favourite
으로const [deleteToggle, onDeleteToggle] = useToggle(false);
...
{deleteToggle && (
<ModalBasic
content="정말 탈퇴하시겠습니까?"
onButtonClick={deleteUser}
toggleState={true}
setToggleState={onDeleteToggle}
/>
)}
모달에서 토글이 필요한 이유는 창 닫기를 위함이다. 즉 언제나 토글 on 상태여야 해서 props로 true
를 넘겨주어 함께 동작하도록 변경했다.
물론 props로 넘기지 않고 기본값을 설정해도 되지만, 어차피 상위 컴포넌트에서 쓰이는 토글 함수를 넘겨주어서(setToggleState={onDeleteToggle}
) 형식을 맞춰줄 겸 함께 넘겼다.
✔️ 마이페이지
남은 작업
✔️ 회원정보 수정