인프런 커뮤니티 질문&답변
문의 드립니다. 팔로우 언팔로우 부분을 끝으로 강의해주신 부분을 테스트중에 회원 가입 부분을 실행해보니 아래와 같은 오류가 발생합니다. 런타임
해결된 질문
작성
·
220
0
회원 가입페이지 자체가 열리지 않습니다.

AppLayout.js
import React from "react";
import PropTypes from 'prop-types';
import Link from 'next/link';
import { Menu, Input, Row, Col } from 'antd';
import styled from 'styled-components';
import { useSelector} from 'react-redux';
import UserProfile from '../components/UserProfile';
import LoginForm from '../components/LoginForm';
const SearchInput = styled(Input.Search)`
vertical-align: middle;
`;
const AppLayout = ({children}) => {
const me = useSelector((state) => state.user.me?.id);
// const inputStyle = useMemo(() => ({ verticalAlign: 'middle' }), []);
return (
<div>
<Menu mode="horizontal">
<Menu.Item>
<Link href="/"><a>노드버드</a></Link>
</Menu.Item>
<Menu.Item>
<Link href="/profile"><a>프로필</a></Link>
</Menu.Item>
<Menu.Item>
<SearchInput enterButton />
</Menu.Item>
<Menu.Item>
<Link href="/signup"><a>회원가입</a></Link>
</Menu.Item>
</Menu>
<Row gutter={8}>
<Col xs={24} md={6}>
{me
? <UserProfile />
: <LoginForm />}
</Col>
<Col xs={24} md={12}>
{children}
</Col>
<Col xs={24} md={6}>
</Col>
</Row>
</div>
);
};
AppLayout.propTypes = {
children: PropTypes.node.isRequired,
};
export default AppLayout;
signup.js
import React, { useCallback, useState, useEffect } from 'react';
import { Form, Input, Checkbox, Button } from 'antd';
import { useDispatch, useSelector } from 'react-redux';
import Router from 'next/router';
import Head from 'next/head';
import styled from 'styled-components';
import { SIGN_UP_REQUEST } from '../reducers/user';
import useInput from '../hooks/useInput';
import AppLayout from '../components/AppLayout';
const ErrorMessage = styled.div`
color: red;
`;
const Signup = () => {
const dispatch = useDispatch();
const [ signUpLoading, me, signUpDone ] = useSelector((state) => state.user);
const [email, onChangeEmail] = useInput('');
const [nickname, onChangeNickname] = useInput('');
const [password, onChangePassword] = useInput('');
const [passwordCheck, setPasswordCheck] = useState('');
const [passwordError, setPasswordError] = useState(false);
const [term, setTerm] = useState(false);
const [termError, setTermError] = useState(false);
useEffect(() => {
if (me) {
alert('로그인했으니 메인페이지로 이동합니다.');
Router.push('/');
}
}, [me && me.id]);
useEffect(() => {
if (signUpDone) {
Router.replace('/');
}
}, [signUpDone]);
const onSubmit = useCallback(() => {
if (password !== passwordCheck) {
return setPasswordError(true);
}
if (!term) {
return setTermError(true)
}
console.log(email, nickname, password )
return dispatch({
type: SIGN_UP_REQUEST,
data: {
email,
password,
nickname
},
});
}, [email, password, passwordCheck, term])
const onChangePasswordCheck = useCallback((e) => {
setPasswordCheck(e.target.value);
setPasswordError(e.target.value !== password);
}, [password]);
const onChangeTerm = useCallback((e) => {
setTerm(e.target.checked);
setTermError(false);
}, []);
return (
<AppLayout>
<Head>
<meta charSet="utf-8" />
<title>회원가입 | NodeBird</title>
</Head>
<Form onFinish={onSubmit} style={{ padding: 10 }}>
<div>
<label htmlFor="user-email">이메일</label>
<br />
<Input name="user-email" type="email" value={email} required onChange={onChangeEmail} />
</div>
<div>
<label htmlFor="user-nickname">닉네임</label>
<br />
<Input name="user-nickname" value={nickname} required onChange={onChangeNickname} />
</div>
<div>
<label htmlFor="user-password">비밀번호</label>
<br />
<Input name="user-password" type="password" value={password} required onChange={onChangePassword} />
</div>
<div>
<label htmlFor="user-password-check">비밀번호 체크</label>
<br />
<Input
name="user-password-check"
type="password"
value={passwordCheck}
required
onChange={onChangePasswordCheck}
/>
{passwordError &&
<ErrorMessage style={{ color: 'red'}}>
비밀번호가 일치하지 않습니다.
</ErrorMessage>
}
</div>
<div>
<Checkbox
name="user-term"
checked={term}
onChange={onChangeTerm}>
제로초 말을 잘 들을 것을 동의합니다.
</Checkbox>
{termError &&
<ErrorMessage style={{ color: 'red' }}>
약관에 동의하셔야 합니다.
</ErrorMessage>
}
</div>
<div style={{ marginTop: 10 }}>
<Button type="primary" htmlType="submit" loading={signUpLoading}>가입하기</Button>
</div>
</Form>
</AppLayout>
);
};
export default Signup;
reducers/user.js
import produce from 'immer';
export const initialState = {
logInLoading: false, // 팔로우 시도중.. 로딩창을 띄운다.
logInDone: false,
logInError: null,
logOutLoading: false, // 언팔로우 시도중.. 로딩창을 띄운다.
logOutDone: false,
logOutError: null,
followLoading: false, // 로그인 시도중.. 로딩창을 띄운다.
followDone: false,
followError: null,
unfollowLoading: false, // 로그인 시도중.. 로딩창을 띄운다.
unfollowDone: false,
unfollowError: null,
signUpLoading: false, // 회원가입 시도중.. 로딩창을 띄운다.
signUpDone: false,
signUpError: null,
changeNicknameLoading: false, // 닉네임 변경 시도중.. 로딩창을 띄운다.
changeNicknameDone: false,
changeNicknameError: null,
isLoggingIn: false,
isLoggedIn: false,
isLoggingOut: false,
me: null,
signUpData: {},
loginData: {},
};
export const LOG_IN_REQUEST = 'LOG_IN_REQUEST';
export const LOG_IN_SUCCESS = 'LOG_IN_SUCCESS';
export const LOG_IN_FAILURE = 'LOG_IN_FAILURE';
export const LOG_OUT_REQUEST = 'LOG_OUT_REQUEST';
export const LOG_OUT_SUCCESS = 'LOG_OUT_SUCCESS';
export const LOG_OUT_FAILURE = 'LOG_OUT_FAILURE';
export const SIGN_UP_REQUEST = 'SIGN_UP_REQUEST';
export const SIGN_UP_SUCCESS = 'SIGN_UP_SUCCESS';
export const SIGN_UP_FAILURE = 'SIGN_UP_FAILURE';
export const CHANGE_NICKNAME_REQUEST = 'CHANGE_NICKNAME_REQUEST';
export const CHANGE_NICKNAME_SUCCESS = 'CHANGE_NICKNAME_SUCCESS';
export const CHANGE_NICKNAME_FAILURE = 'CHANGE_NICKNAME_FAILURE';
export const FOLLOW_REQUEST = 'FOLLOW_REQUEST';
export const FOLLOW_SUCCESS = 'FOLLOW_SUCCESS';
export const FOLLOW_FAILURE = 'FOLLOW_FAILURE';
export const UNFOLLOW_REQUEST = 'UNFOLLOW_REQUEST';
export const UNFOLLOW_SUCCESS = 'UNFOLLOW_SUCCESS';
export const UNFOLLOW_FAILURE = 'UNFOLLOW_FAILURE';
export const ADD_POST_TO_ME = 'ADD_POST_TO_ME';
export const REMOVE_POST_OF_ME = 'REMOVE_POST_OF_ME';
export const ADD_COMMENT_TO_ME = 'ADD_COMMENT_TO_ME';
export const REMOVE_COMMENT_OF_ME = 'REMOVE_COMMENT_OF_ME';
const dummyUser = (data) => ({
...data,
nickname: '제로초',
id: 1,
// sequlize 에서 합쳐주는 데이터라서 앞글자가 대문자 이다.
Posts: [{ id: 1 }], // 내가 쓴 게시글
Followings: [{nickname: '부기초' }, { nickname: 'Chanho Lee' }, { nickname: 'neue zeal'}], // 내 팔로윙들...
Followers: [{nickname: '부기초' }, { nickname: 'Chanho Lee' }, { nickname: 'neue zeal'}], // 내 팔로워들...
});
export const loginAction = (data) => {
return {
type: LOG_IN_REQUEST,
data,
};
};
export const logoutAction = () => {
return {
type: LOG_OUT_REQUEST,
};
};
export const changeNickname = () => {
return {
type: CHANGE_NICKNAME_REQUEST,
};
};
// return 이 생략된다.
// const reducer = (state = initialState, action) => return produce (state, (draft) => {
const reducer = (state = initialState, action) => produce (state, (draft) => {
switch (action.type) {
case FOLLOW_REQUEST:
draft.followLoading = true;
draft.followError = null;
draft.followDone = false;
break;
case FOLLOW_SUCCESS:
draft.followLoading = false;
draft.me.Followings.push({ id: action.data });
draft.followDone = true;
// me: action.data, => me: { ...action.data, nicname: 'zerocho' }, => dummyUser{ action.data },
break;
case FOLLOW_FAILURE:
draft.followLoading = true; // isLoggingOut: true, => logOutLoading: true,
draft.followError = action.error; // logInError: action.error, => logOutDone: false,
break;
case UNFOLLOW_REQUEST:
draft.unfollowLoading = true;
draft.unfollowError = null;
draft.unfollowDone = false;
break;
case UNFOLLOW_SUCCESS:
draft.unfollowLoading = false;
draft.unfollowDone = true;
draft.me.Followings = draft.me.Followings.filter((v) => v.id !== action.data);
// me: action.data, => me: { ...action.data, nicname: 'zerocho' }, => dummyUser{ action.data },
break;
case UNFOLLOW_FAILURE:
draft.unfollowLoading = true; // isLoggingOut: true, => logOutLoading: true,
draft.unfollowError = action.error; // logInError: action.error, => logOutDone: false,
break;
case CHANGE_NICKNAME_REQUEST:
draft.changeNicknameLoading = true; // 닉네임 변경 시도중이니까 => 버튼 로딩 O
draft.changeNicknameDone = false; // 닉네임 변경중
draft.changeNicknameError = null;
break;
case CHANGE_NICKNAME_SUCCESS:
draft.changeNicknameLoading = false; // 닉네임 변경 요청이 성공했으니까 => 버튼 로딩 X
draft.changeNicknameDone = true; // 닉네임 변경 완료
break;
case CHANGE_NICKNAME_FAILURE:
draft.changeNicknameLoading = false; // 닉네임 변경 요청이 끝났으니까 => 버튼 로딩 X
draft.changeNicknameError = action.error;
break;
case SIGN_UP_REQUEST:
draft.signUpLoading = true; // 회원가입 시도중이니까 => true isLoggingOut: true, => logInLoading: true,
draft.signUpDone = false;
draft.signUpError = null;
break;
case SIGN_UP_SUCCESS:
draft.signUpLoading = false; // 회원가입 요청이 성공했으니까 => false isLoggingOut: false, => logOutLoading: false,
draft.signUpDone = true; // isLoggedIn: false, => logOutDone: true,
break;
case SIGN_UP_FAILURE:
draft.signUpLoading = false; // 회원가입 요청이 끝났으니까 => false isLoggingOut: false, => logOutLoading: false,
draft.signUpError = action.error; // 추가
break;
case LOG_IN_REQUEST:
draft.logInLoading = true;
draft.logInError = null;
draft.logInDone = false;
break;
case LOG_IN_SUCCESS:
draft.logInLoading = false;
draft.logInDone = true;
draft.me = dummyUser(action.data);
// me: action.data, => me: { ...action.data, nicname: 'zerocho' }, => dummyUser{ action.data },
break;
case LOG_IN_FAILURE:
draft.logInLoading = true; // isLoggingOut: true, => logOutLoading: true,
draft.logInError = action.error; // logInError: action.error, => logOutDone: false,
break;
case LOG_OUT_REQUEST:
draft.logOutLoading = true; // 로그아웃 시도중이니까 => true
draft.logOutDone = false;
draft.logOutError = null;
break;
case LOG_OUT_SUCCESS:
draft.logOutLoading = false; // 로그아웃 요청이 성공했으니까 => false isLoggingOut: false, => logOutLoading: false,
draft.logOutDone = true; // isLoggedIn: false, => logOutDone: true,
draft.me = null;
break;
case LOG_OUT_FAILURE:
draft.isLoggingOut = false; // 요청이 끝났으니까 => false isLoggingOut: false, => logOutLoading: false,
draft.logOutError = action.error; // 추가
break;
case ADD_POST_TO_ME:
draft.me.Posts.unshift({ id: action.data });
break;
// me: {
// ...state.me,
// Posts: [{ id: action.data }, ...state.me.Posts],
// },
// };
case REMOVE_POST_OF_ME:
draft.me.Posts = draft.me.Posts.filter((v) => v.id !== action.data);
break;
// return {
// ...state,
// me: {
// ...state.me,
// Posts: state.me.Posts.filter((v) => v.id !== action.data),
// },
// };
// case ADD_COMMENT_TO_ME:
// break;
// me: {
// ...state.me,
// Posts: [{ id: action.data }, ...state.me.Posts],
// },
// };
// case REMOVE_COMMENT_OF_ME:
// break;
// me: {
// ...state.me,
// Posts: state.me.Posts.Comments.filter((v) => v.id !== action.data),
// },
// };
default:
break;
// default:
// return state;
// default: {
// return {
// ...state,
// };
// }
}
});
export default reducer;
sagas/user.js
// all fork call put delay debounce throttle takeLatest tabkeEvery takeLeding taekMaybe
import { all, put, fork, takeLatest, delay } from 'redux-saga/effects';
import axios from 'axios';
import {
LOG_IN_REQUEST,
LOG_IN_SUCCESS,
LOG_IN_FAILURE,
LOG_OUT_REQUEST,
LOG_OUT_SUCCESS,
LOG_OUT_FAILURE,
SIGN_UP_REQUEST,
SIGN_UP_SUCCESS,
SIGN_UP_FAILURE,
FOLLOW_REQUEST,
FOLLOW_SUCCESS,
FOLLOW_FAILURE,
UNFOLLOW_REQUEST,
UNFOLLOW_SUCCESS,
UNFOLLOW_FAILURE,
} from '../reducers/user';
function followAPI(data) {
return axios.post('/api/follow', data);
}
function* follow(action) {
try {
// yield put({
// type: 'FOLLOW_REQUEST',
// });
// const result = yield call(followAPI, action.data);
// ex> const result = yield call(logInAPI, action.data, a, b, c); a, b, c 인자를 추가시
yield delay(1000);
// throw new Error('') // 아래 catch 로 넘어간다.
yield put({
type: FOLLOW_SUCCESS,
data: action.data,
});
} catch (err) {
yield put({ // put => dispatch 다.
type: FOLLOW_FAILURE,
data: err.response.data,
});
}
}
function unfollowAPI(data) {
return axios.post('/api/unfollow', data);
}
function* unfollow(action) {
try {
// yield put({
// type: 'UNFOLLOW_REQUEST',
// });
// const result = yield call(unfollowAPI, action.data);
// ex> const result = yield call(logInAPI, action.data, a, b, c); a, b, c 인자를 추가시
yield delay(1000);
// throw new Error('') // 아래 catch 로 넘어간다.
yield put({
type: UNFOLLOW_SUCCESS,
data: action.data,
});
} catch (err) {
yield put({ // put => dispatch 다.
type: UNFOLLOW_FAILURE,
data: err.response.data,
});
}
}
function signUpAPI(data) {
return axios.post('/api/logout', data);
}
function* signUp(action) {
try {
// yield put({
// type: 'LOG_IN_REQUEST',
// });
// const result = yield call(signUpAPI, action.data);
// ex> const result = yield call(logInAPI, action.data, a, b, c); a, b, c 인자를 추가시
yield delay(1000);
// throw new Error('') // 아래 catch 로 넘어간다.
yield put({
type: SIGN_UP_SUCCESS,
data: action.data,
});
} catch (err) {
yield put({ // put => dispatch 다.
type: SIGN_UP_FAILURE,
data: err.response.data,
});
}
}
function logOutAPI() {
return axios.post('/api/logout');
}
function* logOut() {
try {
// yield put({
// type: 'LOG_OUT_REQUEST',
// });
// const result = yield call(logOutAPI);
yield delay(1000);
yield put({
type: LOG_OUT_SUCCESS,
});
} catch (err) {
yield put({ // put => dispatch 다.
type: LOG_OUT_FAILURE,
error: err.response.data,
});
}
}
function* logIn(action) {
try {
console.log('saga logIn');
// yield put({
// type: 'LOG_IN_REQUEST',
// });
// const result = yield call(logInAPI, action.data);
// ex> const result = yield call(logInAPI, action.data, a, b, c); a, b, c 인자를 추가시
yield delay(1000);
yield put({
type: LOG_IN_SUCCESS,
data: action.data,
});
} catch (err) {
yield put({ // put => dispatch 다.
type: LOG_IN_FAILURE,
error: err.response.data,
});
}
}
{/*
takeLeading - 1번째 클릭만 실행
takeLatest - 마지막 클릭만 실행 ( [프론트 서버에서만] 먼저 실행한 부분은 놔두고 동시에 여러게가 실행된 경우만 마지막 이벤트만을 실행한다. )
takeEvery - 1번 사용하면 사라지는 take 대신... while 문(반복문)을 사용하지 않고도.. 계속 재사용할수 있도록 해준다.
*/}
function* watchFollow() {
yield takeLatest(FOLLOW_REQUEST, follow); // take =>logIn 액션이 실행될때까지 기다리겠다.
}
function* watchUnFollow() {
yield takeLatest(UNFOLLOW_REQUEST, unfollow); // take =>logIn 액션이 실행될때까지 기다리겠다.
}
function* watchLogIn() {
yield takeLatest(LOG_IN_REQUEST, logIn); // take =>logIn 액션이 실행될때까지 기다리겠다.
}
function* watchLogOut() {
yield takeLatest(LOG_OUT_REQUEST, logOut);
}
function* watchSignUp() {
yield takeLatest(SIGN_UP_REQUEST, signUp); // take =>logIn 액션이 실행될때까지 기다리겠다.
}
{/*
all 은 배열을 받아서 배열안에 있는 명령들을 한번에 실행 시켜준다.
fork 함수를 실행해준다. call도 있다.
fork[비동기] - 결과를 기다리지 않고 다음 명령 실행을 진행한다. [논블로킹]
call[동기] - 응답을 기다렸다가 응답을 받고 다음 명령을 실행 [블로킹]
*/}
export default function* userSaga() {
yield all([
fork(watchFollow),
fork(watchUnFollow),
fork(watchLogIn),
fork(watchLogOut),
fork(watchSignUp),
]);
}
답변 5
0
castinglife
질문자
오타였네요.. ㅠ.ㅜ 이거 찾는데 반나절 걸린것 같습니다.
> 오타
const [ signUpLoading, me, signUpDone ] = useSelector((state) => state.user);
> 정상
const { signUpLoading, me, signUpDone } = useSelector((state) => state.user);
0
0
0
castinglife
질문자
useInput.js 입니다..
import { useState, useCallback } from 'react';
export default (initValue = null) => {
const [value, setValue] = useState(initValue);
const handler = useCallback((e) => {
setValue(e.target.value);
}, []);
return [value, handler, setValue];
};
0
제로초(조현영)
지식공유자
signup.js에서 19줄 46칸에 에러가 있다고 뜨는데요.
500번대 에러면 브라우저를 보시는 게 아니라 프론트서버쪽 로그도 같이 보셔야 합니다.
useInput은 어떻게 되어있나요(hooks/useInput)
콘솔창에서 uncaught 하고 에러 뜨는데 signup 34:43 이렇게 뜨는데 거기 눌러보시면 어떤 부분에서 에러난건지도 뜹니다.




