블로그 API 클라이언트와 연결하기 -2-
Photo by Kostiantyn Li on Unsplash
회원가입 페이지
import "./register.scss"
import { Link } from "react-router-dom"
import { useState } from "react"
import axios from "axios";
export default function Register() {
const [username,setUsername] = useState("");
const [email,setEmail] = useState("");
const [password,setPassword] = useState("");
const [error,setError] =useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setError(false);
try {
const res = await axios.post("http://localhost:5000/api/auth/register", {
username,
email,
password,
});
console.log(res);
res.data && window.location.replace("/login");
} catch (err) {
console.log(err);
setError(true);
}
}
return (
<div className="register">
<span className="register-title">회원가입</span>
<form className="register-form" onSubmit={handleSubmit}>
<label>User name</label>
<input
type="text"
placeholder="이름을 입력해주세요 ..."
className="register-input"
onChange={e => setUsername(e.target.value)}
/>
<label>Email</label>
<input
type="text"
placeholder="이메일을 입력해주세요 ..."
className="register-input"
onChange={e => setEmail(e.target.value)}
/>
<label>Password</label>
<input
type="password"
placeholder="비밀번호를 입력해주세요 ..."
className="register-input"
onChange={e => setPassword(e.target.value)}
/>
<button type="submit" className="register-button">회원가입</button>
</form>
<span className="register-login">
이미 회원이신가요? <Link to="/login" className="link">로그인</Link> 하러가기
</span>
{error && <span style={{color:"red"}}>이미 가입된 회원입니다.</span>}
</div>
)
}
아무정보도 없이 회원가입 요청을 보냈을때 응답
--->클라이언트에서 에러핸들링 (정보를 입력해달라는 메시지)
정보를 모두 입력했을때
같은정보로 한번 더 회원가입 했을때
---> 라우터에서 에러핸들링 (username, email 이 중복될때 조건 추가, 문맥에 맞는 err제공)
에러핸들링 try..catch로
console.log(err)
const [error,setError] = useState(false);
에러 상태 false로
회원가입 버튼을 눌렀을때 handleSubmit 실행 -> e.preventDefault()로 새로고침 방지 -> setError(false)
에러 false로 초기화 -> axios 요청 실행 (input value를 body에 전달) ->
- 성공시 로그인 페이지로 이동
- 실패시 catch문으로 -> setError(true)로 에러상태로 만듦 -> Error상태가 true일때 나타나는 에러메시지 출력
context API 사용하기
context를 위한 폴더 생성
//context.js
import { createContext, useReducer } from "react";
import Reducer from "./Reducer";
// 1.모든 렌더링 작업이 성공적으로 끝났을때 이 값을 업로드
const INITIAL_STATE = {
user: null,
inFetching: false,
error: false,
}
// 2.이 값(INITIAL_STATE)을 가지고 콘텍스트 프로바이더 내보내기
export const Context = createContext(INITIAL_STATE);
// 3.콘텍스트에 접근하는 방법
export const ContextProvider = ({children}) => {
//13. state dispatch하기, state,dispatch의 값은 Redcucer에서 가지고온다.
//14. -> useReducer(Redcucer) -> Reducer.js에서 Reducer가져오기
//15. 여기서 reducer가 사용하는 state는 INITIAL_STATE다. -> ,INITIAL_STATE)
// 이제 Context를 이 Provider로 줄 수 있음
const [state, dispatch] = useReducer(Reducer, INITIAL_STATE);
//16. context.Provider의 props로 user,isFetching,error를 state에서 가져오고, 마지막으로dispatch한다.
// ex 로그인 버튼을 누르면 이 정보들을 dispatch -> 서버에 응답에 따라 dispatch SUCCESFUL 이나 FAILURE를 가져옴
//17. 이제 ContextProvider를 App.js나 index.js에서 사용가능
// index.js에서 <App/>을 감싸게 되면 모든 곳에서 Context 사용가능 -> 18.index.js로
return(
<Context.Provider value={{
user:state.user,
isFetching:state.isFetching,
error:state.error,
dispatch,
}}>{children}</Context.Provider>
)
}
// 4.로그인 버튼을 눌렀을때 유저네임과 패스워드 두가지를 컨트롤해야한다 credential이 끝난 후
// 추가로 해야하는 일이 있다. 성공이냐 실패냐 일때
// 성공시 INITIAL_STATE 의 프로퍼티들을 업데이트 user: abcd, email:asdfas...
// 실패시 (인가실패 몽고디비에러 등등) -> INITAL STATE의 error 값 바뀜
// 5. -> 이걸 Actions에서 컨트롤
//Actions.js
// 6.타입 -> 액션이름이;라 생각하면 됨
export const LoginStart = (userCredentials) => ({
type: "LOGIN_START"
})
//7. payload -> user를 업데이트 하기 위한 프로퍼티
export const LoginSuccess = (user) => ({
type: "LOGIN_SUCCESS",
payload: user,
})
// 8.에러발생시 - 아무 페이로드도 인수로 집어넣지 않았다.
export const LoginFailure = () => ({
type: "LOGIN_FAILURE"
})
// 9.이제 이 액션들을 가지고 state를 다룰 수 있는게 reducer임
const Reducer = (state, action) => {
//10.switch로 제어
switch(action.type){
case "LOGIN_START":
return {
user:null,
insFetching:true,
error:false,
};
//10.payload에 있는 user를 user값으로 바꿔준다.
//11.fetch는 끝났으니 false로 (작업을 여기서 끝내야되니까)
case "LOGIN_SUCCESS":
return {
user:action.payload,
isFetching: false,
error:false,
}
// 11. error 발생상황이니 error 는 true로
// 작업은 끝난상태니 isFetching은 여전히 false
case "LOGIN_FAILURE":
return {
user:null,
isFetching: false,
error:true,
}
default:
return state;
}
}
//12. 이제 Action과 Reducer를 사용하려면 이것들을 dispatch하면 된다. ->Context로
export default Reducer;
//index.js
import React from 'react';
// import ReactDOM from 'react-dom';
import * as ReactDOMClient from 'react-dom/client'
import App from './App';
import { ContextProvider } from './context/Context';
// ReactDOM.render(
// <React.StrictMode>
// <App />
// </React.StrictMode>,
// document.getElementById('root')
// );
const rootElement = document.getElementById('root');
// 18. ContextProvider로 context를 줄 컴포넌트 감싸기 -> App을 감싸게 되면 모든곳에서 사용가능
const root = ReactDOMClient.createRoot(rootElement);
root.render(
<React.StrictMode>
<ContextProvider>
<App callback={() => console.log("rendered")}/>
</ContextProvider>
</React.StrictMode>
);
//login.jsx
import "./login.scss"
import { Link } from "react-router-dom"
import axios from "axios";
import { useContext, useRef } from "react";
import { Context } from "../../context/Context";
export default function Login() {
const userRef = useRef();
const passwordRef = useRef();
const { user, dispatch, isFecthing } = useContext(Context);
const handleSubmit = async (e) => {
e.preventDefault();
dispatch({type:"LOGIN_START"});
try {
const res = await axios.post("http://localhost:5000/api/auth/login", {
username: userRef.current.value,
password: passwordRef.current.value,
})
dispatch({ type:"LOGIN_SUCCESS", payload: res.data });
} catch (err) {
dispatch({ type:"LOGIN_FAILURE" })
}
}
console.log(user);
return (
<div className="login">
<span className="login-title">환영합니다.</span>
<form className="login-form" onSubmit={handleSubmit}>
<label>username</label>
<input ref={userRef} type="text" placeholder="이름을 입력해주세요 ..." className="login-input" />
<label>Password</label>
<input ref={passwordRef} type="password" placeholder="비밀번호를 입력해주세요 ..." className="login-input"/>
<button type="submit" className="login-button">로그인</button>
</form>
<button className="login-register-button">
<Link to="/register" className="link">회원가입</Link>
</button>
</div>
)
}
React - useRef
React - Ref 와 DOM
이번엔 인풋값을 useRef()
이용하여 가져온다.
-
userRef.current.value
useRef의 current에 담고있는 값들을 사용할 수 있다. -
input ref={userRef}
const userRef = useRef();
useRef()
의 대상으로ref
를 이용해 DOM 엘레멘트를 불러올 수 있다. -
context 가져오기
const {불러올콘텍스트state1,state2... , dispatch} = useContext(불러올콘텍스트);
-
dispatch({type:"LOGIN_START"});
로그인을 시작하기 위해 콘텍스트 값을 로그인을 시작한는 액션 이름인 "LOGIN_START"를 dispatch로 불러온다. -
dispatch({ type:"LOGIN_SUCCESS", payload: res.data });
로그인에 성공했을경우 "LOGIN_SUCCESS" 액션을 불러오고,res.data
를payload
에 할당한다. -
dispatch({ type:"LOGIN_FAILURE" })
에러발생시 "LOGIN_FAILURE" 로 값을 바꾼다.
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
라는 에러가 발생했다.
요청은 한번에 하나만 할 수 있는데 두번이상 들어가면 나오는 에러라고 한다.
그런데 포스트맨에서는 또 잘만 된다...
리액트 라우터 돔 문제인 것 같아서 App에 있는 라우터 구조를 바꿔봐도 해결이 안됬다.
결국 라우터안의 조건문을 if
와 else if
조건문으로 바꿔서 해결했다.
//LOGIN
router.post("/login", async (req, res) => {
try {
console.log(req,"req")
//바꾸기 전 코드
//const user = await User.findOne({ username: req.body.username });
//!user && res.status(400).json("아이디 혹은 비밀번호가 틀렸습니다.(사실 아이디가 틀림)");
//const validated = await bcrypt.compare(req.body.password, user.password);
//!validated && res.status(400).json("아이디 혹은 비밀번호가 틀렸습니다.(사실 비밀번호가 틀림)");
//const { password, ...나머지정보들 } = user._doc;
//res.status(200).json(나머지정보들);
//console.log("요청성공")
// 에러 해결
const user = await User.findOne({ username: req.body.username })
const validated = await bcrypt.compare(req.body.password, user.password);
if (!user) {
console.log("아이디문제")
res.status(400).json("아이디 혹은 비밀번호가 틀렸습니다.(사실 아이디가 틀림)");
} else if(!validated) {
console.log("비번문제");
return res.status(400).json("아이디 혹은 비밀번호가 틀렸습니다.(사실 비밀번호가 틀림)");
} else {
const { password, ...나머지정보들 } = user._doc;
res.status(200).json(나머지정보들);
console.log("요청성공")
}
} catch (err) {
console.log("에러발생")
res.status(500).json(err);
}
})
서버에서 만들어놓은 에러메세지 출력하기
백엔드에서 만들었던 에러메세지를 출력하려고 res로 접근하려 했더니 정의되지 않았다고 뜬다.
catch가 받는 err객체를 console.dir(err)
로 살펴보니 api를 만들때 만들어놓았던 메시지가 data
에 들어가있다.
err객체로 접근해서 err.response.data
를 출력하면 서버(auth.js)에서 지정한 메시지를 출력할 수 있었다.
- 로그인 성공시 응답
Author And Source
이 문제에 관하여(블로그 API 클라이언트와 연결하기 -2-), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@mhnormal/블로그-API-클라이언트와-연결하기-2-저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)