Node & React basic #16

Jay·2023년 5월 17일
0

Node & React basic

목록 보기
16/21
post-thumbnail

Class vs Function Component

Class component가 더 기능이 많지만 코드가 길어지고 처리가 느릴 수 있다.
그러나 React 16.8부터 Hooks가 등장해서 Function component에서도 기능을 다 사용할 수 있다!
예를들어 라이프사이클을 useEffect등으로 구현할 수 있게 됨.

로그인 페이지

State

/login path로 들어갈 수 있는 로그인 페이지에 html과 css를 좀 끼얹어서 이렇게 됐지만, onChange에 아무 함수도 주지 않아서 타이핑을 칠 수가 없음.

그러니 email과 password를 위한 state를 만들어주자.

const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");

const onEmailHandler = (event) => {
    setEmail(event.target.value);
  };

  const onPasswordHandler = (event) => {
    setPassword(event.target.value);
  };

이렇게 state를 만들고 onChange에 함수를 할당해줬다.
원래 나는 이런 경우 const [form, setForm] = React.useState({email: "", password: ""}) 같이 만드는 것을 선호하지만 서버에 보내거나 redux와 연결시키거나 할 때 강의와 다르면 좀 곤란할 수 있으니 강의를 따라서 state를 따로 주고 있다.

onSubmitHandler

그럼 저 email과 password라는 state가 submit됐을 때 백엔드에 보내줘야 하니까 login 버튼에 type='submit' 속성을 주고 form에 onSubmit={onSubmitHandler}핸들러를 준 다음 onSubmitHandler 함수를 작성해보자.

const onSubmitHandler = (event) => {
  event.preventDefault();

  let body = {
    email: email,
    password: password,
  };

  Axios.post("/api/user/login", body).then((response) => {});
};

Axios도 물론 import 해줘야 한다.
preventDefault를 해야 하는 이유는 자명하고..
backend에서 구현했던 login기능과 똑같은 엔드포인트를 준다음 저 response를 보내서 어찌어찌 하면 되는데, 우리는 redux를 쓰기로 했으니까 여기서 조금 복잡해진다.

Redux

import {useDispatch} from 'react-redux'로 useDispatch를 import하자.

전에 봤던 그림을 다시 참고해 보자.

저 dispatch로 action을 취한 다음 reducer, store 순으로 가게 될 것이다.
state 선언부 위에 const dispatch = useDispatch()로 dispatch를 선언하고, dispatch를 통해 action을 부를 준비를 하자.
onSubmit함수 안에 dispatch(loginUser(body));를 넣어주었는데 이는 dispatch를 통해 body정보를 가지고 loginUser라는 action을 취할 것이라는 뜻이다.
하지만 우리에겐 아직 action이 없으니 user_action.js라는 이름으로 새 action을 만든다.

그리고 아까 작성한 axios를 여기에 적어준다.

user_action.js

import axios from "axios";

export function loginUser(dataToSubmit) {
  const request = axios.post("/api/user/login", body).then((response) => {});

  return {}
}

request가 response로부터 넘어온 값을 저장할 것이다.

reducer에서는 previousState와 action을 조합해서 다음 state를 만들기때문에 저 return을 통해서 reducer에 보내는 작업을 해줘야한다!

action의 예시로 {type: 'FETCH_USER_SUCCESS', response: {id: 3, name: 'Mary'}}이런 게 있다.
우리도 type과 res가 필요하다.
우리 action에서는 이를 payload라고 적겠다.

user_action.js

import axios from "axios";

export function loginUser(dataToSubmit) {
  const request = axios.post("/api/user/login", dataToSubmit).then((response) => response.data);

   return {
    type: "LOGIN_USER",
    payload: request
  };
}

(일반적으로 응답데이터를 전송할 때 data필드를 사용한다고 한다.)
이제 이걸 reducer에 보내주어야 한다.

그 전에 수정할 것이 있다.
우리는 type만 관리하는 js파일을 하나 더 만들 것이다.
_actions 경로에 types.js를 만들어서 export const LOGIN_USER = "login_user";를 적어주자.
그리고 user_action.js에는 import {LOGIN_USER} from './types'를 통해 type을 import한다. 그러면 return부분의 type에 따옴표를 쓰지 않아도 된다(변수화되었기 때문).

reducer에서도 import {LOGIN_USER} from '../_actions/types'를 통해 type을 import해주고

이렇게 만들어 줬음.
initialState는 어떻게 돼 있는지, 저게 어떤 식으로 return이 되는지 감이 잘 안잡히는데 일단 봐야겠음.
(initialState는 초기 Store상태이다 - 나중에 덧붙임)

그럼 이제 처음에 dispatch를 사용하던 loginPage로 가보자.

loginUser라는 action을 작성만하고 import안해줬으니 import {loginUser} from 해주자.
그리고 지난번 챕터에서 만든 reducer/index.js의 주석처리부분을 주석해제한다.

import { combineReducers } from "redux";
import user from './user_reducer';

const rootReducer = combineReducers({
  user,
});

export default rootReducer;

그러고나서 예전에 postman으로 회원가입했던 정보로 로그인하면 redux devtool에

이렇게 나오는데 어떻게해서 이렇게 나오는 거냐면,
우리가 프론트에서 login버튼을 누르면 이 정보는 dispatch에 실려서 loginUser라는 action을 실행하러 간다. loginUser action은 user_action파일에 있는데 받은 정보를 가지고 post방식으로 /api/users/login에 정보를 요청한다. 이 endpoint는 server/index.js에서 줬던 것이다. 여기서 findOne이나 comparePassword등을 통해 유저를 확인한 다음 모든 걸 통과했다면 최종적으로 res.json({loginSuccess: true, userId: user._id})을 보내준다. 이 정보가 user_action의 return을 통해서 LOGIN_USER라는 타입명과 함께 payload에 실려서 reducer폴더의 user_reducer로 간다. 그리고 case LOGIN_USER에 따라 위에서 받은 json정보를 return하는 것이다.

근데 문득 reducer는 어떻게 action이랑 연결이 된건지 모르겠네.. chatGPT한테 물어보니까 redux의 규칙에 의해 전달되는 거라고 한다. 액션이 디스패치를 통해 redux store로 전달되면 store는 액션을 받아 리듀서를 호출한다고.
좀 신기하다.

이제 그럼 제대로 response를 받았으면(로그인이 됐으면) 페이지를 랜딩페이지로 이동하게 하자.
LoginPage.js에서 import { useNavigate } from "react-router-dom"을 한다. 강의에서는 props.history.push를 이용했는데 리액트라우터 v6 이상부터는 useNavigate를 사용해야 한다.
const navigate = useNavigate()를 해 준 다음 dispatch 부분을 수정하자.

dispatch(
  loginUser(body).then((response) => {
  	if (response.payload.loginSuccess) {
    	navigate("/");
    } else {
    	alert("Error!");
    }
  })
)

redux를 이용하는 것이 아니라면 axios모듈에서 지정해준 대로 response.data를 통해 결과값에 접근이 가능하다. redux에서는 redux.payload를 통해 결과값에 접근이 가능하기에 response.payload.loginSuccess를 해주는 것이고 위 이미지에 있듯 이게 true이면(로그인 성공하면) 엔드포인트 /인 랜딩페이지로 간다.

이러고 나서도 문제가 있었는데 Uncaught Error: useNavigate() may be used only in the context of a <Router> component. 에러가 뜨는 거였다. 하지만 이 모든 기능은 LoginPage에 추가되고 있고, LoginPage는 App의 BrowserRouter에 있는데 왜 그런지 알 수가 없었다. 알고보니 차이는 <Route path="/" element={<LandingPage />} /><Route path="/" element={LandingPage()} />에 있었다. 원래 후자로 적혀 있었는데 전자로 바꾸니 잘 작동하였다.

근데.. 이젠 로그인 누르면 시뻘건 에러가..

혹시나 해결될까 해서 dispatch부분 .then이하를 다 주석처리하고 로그인 해봤는데 redux devtool에서 success가 안 뜬다.
진짜 짜증
이건 다음에 auth도 연계해서 하면서 해결해보자
어떤 사람이 이런 글을 써놨더라

profile
ㄱ이 아닌 개발자가 되고 싶은 사람

0개의 댓글