接続ブログ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>
  )
}

何の情報もないまま会員加入要求を送信した場合に応答する

----->クライアントエラーハンドル(プロンプト入力情報)
すべての情報を入力する場合

同じ情報で再度会員に加入した場合.

----->ルータエラー処理(ユーザー名、電子メールを繰り返すときに条件を追加し、コンテキスト関連errを提供する)
エラーハンドルtry.ほそくろconsole.log(err) const [error,setError] = useState(false);エラーステータスfalse
会員加入ボタンを押すとhandleSubmit->e.preventDefault()を実行し、リフレッシュを防止->setError(false)エラー初期化->axiosリクエストの実行(input valueをbodyに渡す)->
  • 成功登録ページ
  • 失敗時catch文の使用->設定エラー(true)->エラーステータスが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.valueuserefの現在の値を使用できます.
  • input ref={userRef} const userRef = useRef(); useRef()を対象として、refを使用してDOM ELMENTをマウントできます.

  • コンテキストのインポートconst {불러올콘텍스트state1,state2... , dispatch} = useContext(불러올콘텍스트);
  • dispatch({type:"LOGIN_START"});ログインを開始するには、コンテキスト値ログインを開始するアクション名「LOGIN START」がdispatchにロードされます.
  • dispatch({ type:"LOGIN_SUCCESS", payload: res.data });ログインに成功すると、「LOGIN SUCCESS」アクションがロードされ、res.datapayloadに割り当てられます.
  • 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というエラーが発生しました.
    リクエストは一度に1つしかできませんが、2回以上入るとエラーになるそうです.
    でもPostmanではよくできていました...
    これはルータドームの問題を反映しているようで、アプリのルータ構造を変えても解決できません.
    最終的には、ルータ内の条件文を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)が指定したメッセージを出力することができる.
  • ログイン成功時応答