View → Action → Dispatcher → Store (Middleware → Reducer) → View
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "100%",
height: "100vh",
}}
>
<form
style={{
display: "flex",
flexDirection: "column",
}}
**onSubmit**={onSubmitHandler}
>
<label>Email</label>
<input type="email" value={Email} **onChange**={**onEmailHandler**} />
<label>Password</label>
<input type="password" value={Password} onChange={**onPasswordHandler**} />
<br />
<button>Login</button>
</form>
</div>
function LoginPage() {
const [Email, setEmail] = useState("");
const [Password, setPassword] = useState("");
const onEmailHandler = (event) => {
setEmail(event.currentTarget.value);
};
const onPasswordHandler = (event) => {
setPassword(event.currentTarget.value);
};
const onSubmitHandler = (event) => {
event.preventDefault();
};
onEmailHandelr
onPasswordHandler
가 실행되어서event.preventDefault()
: 이벤트의 기본동작을 막아줌 (페이지 리프레시)let body = {
email: Email,
password: Password,
};
npm install redux react-redux
LoginPage.js
function LoginPage() {
const dispatch = useDispatch();
const navigate = useNavigate();
const onSubmitHandler = (event) => {
event.preventDefault();
let body = {
email: Email,
password: Password,
};
dispatch(loginUser(body)).then((response) => {
if (response.payload.loginSuccess) {
// props.history.push("/");
navigate("/");
} else {
alert(response.payload.message);
}
});
};
user_action.js
import axios from "axios";
import { LOGIN_USER } from "./types";
export function loginUser(dataToSubmit) {
const request = axios
// 백엔드 서버 url에 dataToSubmit 데이터 보내주기
.post("/api/users/login", dataToSubmit)
.then((response) => response.data);
return {
// redux로 옮겨주기
type: LOGIN_USER,
payload: request,
};
}
then()
메서드는 Promise
를 리턴하고 두 개의 콜백 함수를 인수로 받음login Action 순서
- LoginPage로부터 데이터(email, password)를 받아옴(dataToSubmit)
- axios를 사용하여서 백엔드 서버 url(/api/users/login)에 데이터(dataToSubmit)보내주기
- 백엔드에서 로그인 과정 거쳐서 response를 받는 과정
server/models/User.js
userSchema.methods.comparePassword = function (plainPassword, cb) {
//plainPassword 1234567 암호회된 비밀번호 $2b$10$l492vQ0M4s9YUBfwYkkaZOgWHExahjWC
bcrypt.compare(plainPassword, this.password, function (err, isMatch) {
// 사용자가 입력한 비번(plainPassword)와 db에 저장된 비번과 비교
// 틀리고, 에러가 나면 콜백함수에 err를 반환하고,
// 같다면 콜백함수에 null값과 true값이 있는 isMatch를 반환
if (err) return cb(err);
cb(null, isMatch);
});
};
userSchema.methods.generateToken = function (cb) {
var user = this;
// console.log('user._id', user._id)
// jsonwebtoken을 이용해서 token을 생성하기
var token = jwt.sign(user._id.toHexString(), "secretToken");
// user._id + 'secretToken' = token
// ->
// 'secretToken' -> user._id
user.token = token;
user.save(function (err, user) {
if (err) return cb(err);
cb(null, user);
});
};
server/index.js
app.post("/api/users/login", (req, res) => {
// 요청된 이메일틀 데이터베이스에서 있는지 찾아보기
// findOne() : 요소를 찾는 몽고DB 메소드
User.findOne({ email: req.body.email }, (err, user) => {
// 이메일에 해당하는 유저가 없으면
if (!user) {
return res.json({
loginSuccess: false,
message: "제공된 이메일에 해당하는 유저가 없습니다.",
});
}
// 요청된 이메일이 데이터베이스에 있다면 비밀번호가 맞는 비밀번호인지 확인
user.comparePassword(req.body.password, (err, isMatch) => {
// isMatch가 없으면 비번이 틀림
if (!isMatch)
return res.json({
loginSuccess: false,
message: "비밀번호가 틀렸습니다.",
});
// 비밀번호까지 맞다면 토큰 생성
user.generateToken((err, user) => {
// status(400)->에러
if (err) return res.status(400).send(err);
// 토큰을 저장(쿠키, 로컬스토리지)
// status(200)->성공
res
.cookie("x_auth", user.token)
.status(200)
.json({ loginSuccess: true, userId: user._id });
});
});
});
});
_actions/types.js
export const LOGIN_USER = "login_user";
dispatch(loginUser(body))
_reducers/user_reducer.js
import { LOGIN_USER } from "../_actions/types";
export default function f(state = {}, action) {
switch (action.type) {
case LOGIN_USER:
return { ...state, loginSuccess: action.payload };
default:
return state;
}
}
{…state}
⇒ 기존 state(비어있음)를 복사하는것action.payload
)를 넣어줌_reducers/index.js
import { combineReducers } from "redux";
import user from "./user_reducer";
// reducer가 나눠져 있는데 combineReducer을 이용하여
// root reducer에서 하나로 합침
const rootReducer = combineReducers({
user,
});
export default rootReducer;
combineReducers
함수를 불러워서 합쳐주기dispatch(loginUser(body)).then((response) => {
if (response.payload.loginSuccess) {
// props.history.push("/");
navigate("/");
} else {
alert(response.payload.message);
}
});
props.history.push("/");
react-router-dom v6부터 useHistory 에서 useNavigate로 바뀜
import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
dispatch(loginUser(body)).then((response) => {
if (response.payload.loginSuccess) {
// props.history.push("/");
navigate("/");
} else {
alert(response.payload.message);
}
});
참고
https://www.zerocho.com/category/React/post/57b60e7fcfbef617003bf456