본문 바로가기
React

2021.09.21 React 로그인, 회원가입(2)

by 해맑은 코린이 2021. 9. 21.

2021.09.21 React 로그인, 회원가입(2)_정리노트

 

지금 1편 포스팅 쓰고 새벽 1시 6분이군 내일 일찍 일어나야하므로 3시까지 하고 자야지...요즘 빨리 자나 싶더니 유튜브 보다가 맨날 4~5시에 자는.....젠장 그럴 시간에 코드나 칩시다 그리고 추석전에 끝내야..! 할머니집 가서 ...! 편하게 와구와구 할 수 있으니까 좀만 더 짬짬히 빡시게 달리자

---- 어림도 없지 18일때 저랬는데 지금 21일 벌써 당일임...ㅎ..... 알바 대타도 뛰고 .. 놀기도 놀고....난리였군 다시 채찍질하러 간다. 

 

 

API 를 연동을 드디어 해볼테야. 이제 하나만 하면 된다고 생각하고 얏호했는데 음음 만만치 않아서 gg 하고 2번째 포스팅으로 뺐다^^

ㅋㅋㅋㅋㅋㅋㅋㅋㅋ 뭐보고 쫄았냐면 바로

 

$ yarn add axios redux-saga

 

ㅎㅎ... redux-saga 라이브러리는 redux-thunk 다음으로 가장 많이 사용되는 라이브러리이고, thunk 에서는 하지 못하는 까다로운 비동기 작업들을 처리할 수 있지만, 진입장벽이 거대하다 거대해..^^ ㅎ... 아직 js 도 끙끙에다가 리덕스도 이제 겨우 용어 익숙해지고 있는 저에게는 많이 두렵네요 흑흑 ... 

 

axios 보고 으악 드디어 써보나 두근두근 api 요청 두근두근 하다가 saga 보고 흐억 했음 ㅎ..... 그래도 내 멋진 프로젝트를 위해서 나는. 한다 .오늘도. 화이팅.

 

 

https://react.vlpt.us/redux-middleware/10-redux-saga.html

 

10. redux-saga · GitBook

10. redux-saga 소개 redux-saga는 redux-thunk 다음으로 가장 많이 사용되는 라이브러리입니다. redux-thunk의 경우엔 함수를 디스패치 할 수 있게 해주는 미들웨어였지요? redux-saga의 경우엔, 액션을 모니터

react.vlpt.us

짜아 한번 우리 같이 읽고 넘어가자! 

 

빠샤 해봅시다 설치!!!!!!

 

 

그리고 본격적으로 API 요청을 하기전에 axios 인스턴스를 생성해야하는데, 왜해주냐 우리가 요청을 할 때 어느정도 중복되는 부분이 있기 때문에 baseurl, header 등을 반복할 필요도 없고 , 인스턴스를 만들지 않는다면 모든 요청에 대해 설정하게 되는 것이므로 또 다른 API 르 사용할 때 곤란할 수 있다네여. 처음 개발할 때부터 인스턴스를 만들어서 작업하는 것이 여러모로 이점인 것.

 

src.lib/api/client.js ( 새로 생성 )

import axios from 'axios';

const client = axios.create();

/*
  글로벌 설정 예시:
  
  // API 주소를 다른 곳으로 사용함
  client.defaults.baseURL = 'https://external-api-server.com/' 
  // 헤더 설정
  client.defaults.headers.common['Authorization'] = 'Bearer a1b2c3d4';
  // 인터셉터 설정
  axios.intercepter.response.use(\
    response => {
      // 요청 성공 시 특정 작업 수행
      return response;
    }, 
    error => {
      // 요청 실패 시 특정 작업 수행
      return Promise.reject(error);
    }
  })  
*/

export default client;

 

또또 해줘야할 것 근데 내가 아는 거 나옴 ㅎ

 

https://korinkorin.tistory.com/55

 

2021.05.07 drf & react 연동하기

2021.05.07 drf & react 연동하기_정리노트 나름 빠른..시간안에 온게 맞겠지..? ㅎ.. 진짜 근데 핑계를 대자면..^^ 난리 부르스를 통해서... 해냈다.. 정말 ...ㅠ.. 그 전에 PostMan 이라는 걸로 내 API 테스트.

korinkorin.tistory.com

 

여기서도 설명했듯이 현재 백엔드에서는 4000번 포트 프론트에서는 3000번 포트로 개발서버가 열려있기 때문에 CORS 문제 발생! 

그때는 장고로 설정해주었지만 오늘은 드디어 리액트에서 하는규나~~~

프론트쪽에서 설정해줄 때는 proxy 라는 웹팩 개발 서버에서 지원하는 기능을 사용.

 

proxy는 말그대로 대리해주는, 중계해준다는 뜻인데, 이런 서버를 proxy 서버 라고 하며,

프론트 개발서버와 브라우저, 백엔드 서버와 브라우저 각각 통신하던 것을,

프록시를 통해서 브라우저와 리액트 서버가 응답하고 백엔드 서버는 리액트에서 판단해서 통신을 하게 된다.

절대경로에서 상대경로로 바뀌주는 느낌쓰.

 많이 해줄 것도 없음. package.json 만 건드려 주면 된다.

 

 

 

package.json 

 

크크 이거 하나만 추가하면 리액트에서 client.get('/api/posts') 라고 해줘도 웹팩 개발서버가 알아서 프록시 역할을 하게 되고,

http://localhost:4000/api/posts 로 대신 요청해서 응답을 해준다!

 

 


 

이제 프록시 설정까지 했으니까 API 요청 함수 작성 ㄱ! 프록시 설정 때문에 개발 서버는 다시 껐다가 켜야한다.

 

참고로 각 요청에 대한 경로는

 

 

먼저 post, auth 를 나눠서 

 

왼쪽은 CRUD, 오른쪽은 Login/Register 관련 url 이다. 나중에 리액트에서 요청할 때 따로 또 주석으로 적겠움

 

 

 

src/api/auth.js ( 새로 생성 )

 

import client from "./client";

// 로그인
export const login = ({username, password}) => 
    client.post('/api/auth/login', {username,password});

// 회원가입
export const register = ({username,password}) => 
    client.post('/api/auth/register',{username,password});

// 로그인 상태 확인
export const check = () => client.get('/api/auth/check');

 

 

그리고 드디어  redux-saga 를 통해 더 쉽게 ...? ㅎ.. API를 관리하기 위해서 loading 리덕스 모듈을 만들고 createRequestSaga 유틸 함수를 설정.

 

src/modules/loading.js ( 새로 생성 )

import { createAction, handleActions } from "redux-actions";


// 액션 타입 정의

const START_LOADING = 'loading/START_LOADING';
const FINISH_LOADING = 'loading/FINISH_LOADING';

// 요청을 위한 액션 타입을 payload 로 정의
// 액션 생성 함수

export const startLoading = createAction(
    START_LOADING,
    requestType => requestType,
);

export const finishLoading = createAction(
    FINISH_LOADING,
    requestType => requestType,
)


// 초기 상태 설정

const initialState = {};

// 리듀서 함수
const loading = handleActions(
    {
        [START_LOADING] : (state,action) =>({
            ...state,
            [action.payload] : true,
        }),
        [FINISH_LOADING] : (state,action) => ({
            ...state,
            [action.payload]:false,
        }),
    } ,
    initialState,
);

export default loading;

 

리듀서를 만들어주면 ?? 루트 리듀서에 추가 ㄲ

 

src/modules/lndex.js

import { combineReducers } from "redux";
import auth from "./auth";
// loading import
import loading from "./loading";

// loading add
const rootReducer = combineReducers({
    auth,
    loading ,
});


export default rootReducer;

후.. 이제 createRequestSaga 만들러 ㄲ! ( 반복되는 코드를 함수화해서 줄여주기 위해서)

 

src/lib/createRequestSaga.js

import { call, put } from 'redux-saga/effects';
import { startLoading,finishLoading } from '../modules/loading';

// 타입 정의 함수
export const createRequestActionType = type =>{
    const SUCCESS = `${type}_SUCCESS`;
    const FAILURE = `${type}_FAILURE`;

    return [type,SUCCESS,FAILURE]
};


// request 제너레이터 함수 >> 사가라고 함.
 // 파라미터로 action을 받아오면 액션의 정보 조회 가능.
export default function createRequestSaga(type, request){
    const SUCCESS = `${type}_SUCCESS`
    const FAILURE = `${type}_FAILURE`

    return function*(action){
        yield put(startLoading(type)); // 로딩 시작
        try{
            // call 사용시 Promise 를 반환하는 함수를 호출하고, 기다릴 수 있음.
            // 첫번째 파라미터에는 함수, 나머지 파라미터는 해당 함수에 넣을 인수.
            const response = yield call(request, action.payload);
            // put 은 특정 액션을 디스패치함.
            yield put({
                type:SUCCESS,
                payload:response.data,
            });
            // try catch 문으로 에러 잡기.
        }catch(e){
            yield put({
                type:FAILURE,
                payload:e,
                error:true,
            });
        }
        yield put(finishLoading(type)); // 로딩 완료
    }
};

API 요청시 관련 주석들을 정리해보았움.. 끌끌 요상하다 요상해 하지만 에러도 잡을 수 있고 얘를 통해서 API 사용할 수 있도록 한다니 계속 가봅씨다

 

이렇게까지 적어주었으면, auth 모듈에서 API 를 사용할 수 있도록 구현해야하는데, 

 

src/modules/auth

 

요롷게 6개를 적어줘야하는데 아까 말했듯이 createRequestSaga 에서는 반복되는 부분을 함수화해서 정리해주기 위해서 생성한 것도 있댔는데 요부분을 간단하게 적을 수 있는 부분이

 

src/lib/createRequestSaga

// 타입 정의 함수
export const createRequestActionType = type =>{
    const SUCCESS = `${type}_SUCCESS`;
    const FAILURE = `${type}_FAILURE`;

    return [type,SUCCESS,FAILURE]
};

바로 요기! 이렇게 적어주게 되면, 

 

src/modules/auth

 

이렇게 불러와서 한번에 적어줄 수 있따. 물론 나도 익숙치..않기도해서 흑흑.. 했는데 그냥 내가 주석 처리 해준 대로 그냥 const 로 사실 선언해도 될듯 ㅎ 리팩토링 개념이라 일단 saga 가 뭔지부터 흐름 잡는게 중요할거라고 난 느꼈움~~

 

 

이제는 createRequestSaga 를 이용해서 API 요청을 위한 사가를 생성하고 액션 생성 함수, 리듀서도 구현해보자!

 

 

src/modules/auth

import { createAction, handleActions } from 'redux-actions';
// produce import
import produce from 'immer';
// takeLatest import
import { takeLatest } from 'redux-saga/effects';
// saga import
import createRequestSaga,{ createRequestActionTypes } from '../lib/createRequestSaga';
// api import
import * as authAPI from '../lib/api/auth';


// // sample 액션 타입 정의
// // module/ACTION_TYPE. 액션 타입은 대문자로, 앞에 모듈을 붙여주어야 액션 이름 중첩을 방지할 수 있음.
// const SAMPLE_ACTION = 'auth/SAMPLE_ACTION';

const CHANGE_FIELD = 'auth/CHANGE_FIELD';
const INITIALIZE_FORM = 'auth/INITIALIZE_FORM';

// react-saga api 요청 action
// 회원가입
// const REGISTER = 'auth/REGISTER';
// const REGISTER_SUCCESS = 'auth/REGISTER_SUCCESS';
// const REGISTER_FAILURE = 'auth/REGISTER_FAILURE';

// //로그인
// const LOGIN = 'auth/LOGIN';
// const LOGIN_SUCCESS = 'auth/LOGIN_SUCCESS';
// const LOGIN_FAILURE = 'auth/LOGIN_FAILURE';


const [ REGISTER,REGISTER_SUCCESS, REGISTER_FAILURE ] =createRequestActionTypes(
  'auth/REGISTER',
);

const [ LOGIN,LOGIN_SUCCESS, LOGIN_FAILURE ] = createRequestActionTypes(
  'auth/LOGIN',
);

// // 액션 생성자. createAction 함수는 매번 객체를 직접 만들어 줄 필요 없이 더욱 간단하게 액션 생성 함수를 선언할 수 있음. 사용으로 액션 추가 데이터는 payload 사용. 
// // 액션 생성자는 export
// export const sampleAction = createAction(SAMPLE_ACTION);

export const changeField = createAction(
    // 추가 데이터 생성은 payload.
    CHANGE_FIELD,
    ({form,key,value}) =>({
        form, // register, login
        key, // username , passowrd, passowrdConfirm
        value // 실제로 바꾸려는 값
    })
);

export const initializeForm = createAction(INITIALIZE_FORM, form => form); //register , login


// register/login
export const register = createAction(REGISTER, ( {username, passoword })=> ({
  username,
  passoword,
}));

export const login = createAction(LOGIN, ( { username,password}) => ({
  username,
  password,
}));


// 사가 생성 

const registerSaga = createRequestSaga(REGISTER, authAPI.register);
const loginSaga = createRequestSaga(LOGIN,authAPI.login);

export function* authSaga(){
  // takeLatest는 기존에 진행 중이던 작업이 있다면 취소 처리 하고 가장 마지막으로 실행된 작업만 수행.
  yield takeLatest(REGISTER, registerSaga);
  yield takeLatest(LOGIN,loginSaga);
}


// 초기 상태 정의
const initialState = {
  register: {
    username: '',
    password: '',
    passwordConfirm: ''
  },
  login: {
    username: '',
    password: ''
  },
  auth:null,
  authError:null,
};




// 리듀서 함수
// 리듀서 함수도 더 간단하고 가독성 높게 사용하기 위해 handleActions 함수 사용
// 리듀서 함수는 export default 로 
const auth = handleActions(

{
    // 전부 다 추가 데이터가 payload 면 헷갈리니까 객체 비구조화 할당으로 좀 더 직관적으로 사용
    [CHANGE_FIELD]: (state, { payload: { form, key, value } }) =>
      // 불변성 유지를 위해 immer 라이브러리 사용
      produce(state, draft => {
        draft[form][key] = value; // 예: state.register.username을 바꾼다
      }),
    [INITIALIZE_FORM]: (state, { payload: form }) => ({
    // 얘는 오히려 immer 쓰면 복잡해지니까 spread 사용
      ...state,
      [form]: initialState[form],
      authError:null, // 폼 전환 시 회원 인증 에러 초기화
      
    }),

    [REGISTER_SUCCESS] : ( state, { payload : auth }) => ({
      ...state,
      authError:null,
      auth,
    }),
    [REGISTER_FAILURE] : ( state, { payload : error }) => ({
      ...state,
      authError:error,
      }),

    [LOGIN_SUCCESS] : ( state, { payload : auth }) => ({
      ...state,
      authError:null,
      auth,
    }),
    [LOGIN_FAILURE] : ( state, { payload : error }) => ({
      ...state,
      authError:error,
      }),
  },
  initialState,
);


export default auth;

 

흐어....길당...... 이렇게 구현해주고, 로딩 모듈에 대한 상태는 이미 loading 모듈에서 관리하기 때문에 성공,실패했을 때의 상태에 대해서만 신경쓰면 된다!

 

리덕스 모듈을 작성했으니 rootSaga 작성!

 

modules/index

import { combineReducers } from "redux";
// all import 
import { all } from 'redux-saga/effects';
import auth,{ authSaga } from "./auth";
// loading import
import loading from "./loading";

// loading add
const rootReducer = combineReducers({
    auth,
    loading ,
});

export function* rootSaga(){
    // all 은 배열안에 있는 모든 제너레이터 함수들이 병행적으로 동시에 실행되고, 전부 이행될 때까지 기다림.
    // Promise.all 과 비슷하다고 해서 찾아보니 모든 것들이 이행될 때까지 기다리고 하나라도 에러가 나면 모든 Promise는 무시가 되고, catch 문 실행한다니 이와 비슷할듯
    // 여러개의 사가를 쓸 때 이렇게 써줄 수 있음
    yield all([authSaga()]);
}
export default rootReducer;

아직 saga 에서 쓰는 함수들이 이해가 잘안되는거 같아서 이리저리 구글링하면서 찾는즁..홀홀 쨋든 루트사가 까지 만들었다면, 스토어에 최종적으로 redux-saga 미들웨어를 적용하자.

 

src/index.js

 

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';
// import
import { Provider } from 'react-redux';
import { applyMiddleware, createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
// createSagaMiddleware import
import createSagaMiddleware from 'redux-saga'
// root Saga import
import rootReducer, {rootSaga} from './modules/index';
// saga middleware 생성
const sagaMiddleware = createSagaMiddleware();
// store 적용
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(sagaMiddleware)));
// saga 실행
sagaMiddleware.run(rootSaga);


ReactDOM.render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>,
  document.getElementById('root'),
);

 

회원가입 리덕스 세팅 끝....세상에....휴...

 

 


 

이제 회원가입 구현하러 ㄲ!!!!!

 

src/containers/auth/RegisterForm

// useEffect import 
import React,{useEffect} from 'react';

import { useDispatch, useSelector } from 'react-redux';
// auth module register import
import { changeField, initializeForm,register } from '../modules/auth';

import AuthForm from '../components/auth/AuthForm';


const RegisterForm = () => {
    const dispatch = useDispatch();
    const { form, auth, authError } = useSelector(({ auth }) => ({
      form: auth.register,
      auth: auth.auth,
      authError: auth.authError,
     
    }));
    // input change event 로 액션 디스패치. 디스패치 : 액션을 발생시키는 것.
    const onChange = e => {
        const { value, name } = e.target;
        dispatch(
          changeField({
            form: 'register',
            key: name,
            value,
          }),
        );
      };
    // form submit event 로 register 함수에 현재 username, password 를 파라미터로 넣어서 액션 디스패치. 
    // 사가에 대한 API 요청 처리후, 이에 대한 결과를 auth/authError 를 통해 조회
    const onSubmit = e => {
        e.preventDefault();
        const { username, password, passwordConfirm } = form;
        console.log(e);
        
        
        if (password !== passwordConfirm){
            // 오류처리 할 것임
            return ; 
        }
        dispatch(register({ username, password }));
    };


    // 컴포넌트 초기 렌더링시 form 초기화
    useEffect(()=>{
        // 액션 생성 함수 import
        dispatch(initializeForm('register'));
    },[dispatch]);

    // auth, authError 값 중에서 무엇이 유효한지에 따라 다른 작업을 함.
    useEffect(()=>{
        if (authError){
            console.log('오류 발생');
            console.log(authError);
            return;
        }
        if(auth){
            console.log('회원가입 성공');
            console.log(auth);
        }
    },[auth,authError])

    return (
        
       <AuthForm
       type="register"
       form={form}
       onChange={onChange}
       onSubmit={onSubmit} 
       />
    );
};

export default RegisterForm;

(passwordConfirm 오타 때문에 아무일도 일어나지 않아서 자꾸 폼 submit 나 다른거에 30분 허비한 사람 여기요~)

 

정말! 잘 ! 치자... 허허 나는 개인적으로 오타하나 나서 어디서도 오류가 났는지 모르겠어서 난리를 부렸기 때문..^^ ㅋㅋㅋㅋㅋㅋ 여기서 form submit event 가 발생할 때 액션이 디스패치되고, 사가에서 API 요청을 한 후, 이에 대한 결과를 auth/authError 로 조회하게 하였다. 당장은 에러처리 보다는 API 요청 구현에 초점을 두고!!! 

 

백엔드, 프론트엔드 서버를 둘 다 킨 후에 

 

 

터미널을 나눠서 프론트는 yarn start 로, 백엔드는 그 때 설정했던건데, yarn start:dev 로 켜주었다.

 

 

ㅠㅠㅠㅠㅠ 어우 된다 된다... 콘솔에 성공이라고 찍히고 한번 더 누르면 오류 발생이 찍히면 된다..! 지금 백엔드에 콘솔을ㄹ 찍어본다곸ㅋㅋㅋㅋ 콘솔이 쫌 더러운데 쨋든 한번 누르면 성공 두번째부터는 저렇게 빨간색 409 confilct 가 나면서 오류 발생이 뜨면 성공!

 

 

 

 

 

 

이제는 또... 리덕스...모듈.....user의 상태를 담아야하기 때문에...다시 모듈만들러간다....

 

src/modules/user.js ( 새로 생성 ) 

import { createAction, handleActions } from "redux-actions";
import { takeLatest } from 'redux-saga/effects';
import * as authAPI from '../lib/api/auth';
import createRequestSaga,{
    createRequestActionTypes
} from "../lib/createRequestSaga";


//액션 타입 정의
const TEMP_SET_USER = 'user/TEMP_SET_USER'; // 새로고침 이후 임시 로그인 처리
// 액션 타입 정의 함수 가져와서 한번에 적어주기
const [CHECK,CHECK_SUCCESS,CHECK_FAILURE ] = createRequestActionTypes(
    'user/CHECK',
);


// 액션 생성 함수
export const tempSetUser = createAction(TEMP_SET_USER, user => user);
export const check = createAction(CHECK);


// saga 생성
const checkSaga = createRequestSaga(CHECK,authAPI.check);

export function* userSaga(){
     // takeLatest는 기존에 진행 중이던 작업이 있다면 취소 처리 하고 가장 마지막으로 실행된 작업만 수행.
    yield takeLatest(CHECK,checkSaga);
};

// 초기 상태 정의
const initailState = {
    user: null,
    checkError: null
};


// 리듀서 함수
export default handleActions(
    {
        [TEMP_SET_USER]:(state, {payload: user}) =>({
            ...state,
            user,
        }),

        [CHECK_SUCCESS]:(state, {payload : user }) => ({
            ...state,
            user,
            checkError: null,
        }),
        [CHECK_FAILURE]:(state, {payload : error }) => ({
            ...state,
            user: null,
            checkError: null,
        }),
    },
    initailState,
)

 

후ㅜ... 새로운 모듈을 만들었으니까 루트 리듀서에도 추가해주기 

 

src/modules/index

import { combineReducers } from "redux";
// all import 
import { all } from 'redux-saga/effects';
import auth,{ authSaga } from "./auth";
// loading import
import loading from "./loading";
// user import
import user,{ userSaga } from "./user";
// loading add
const rootReducer = combineReducers({
    auth,
    loading ,
    user
});

export function* rootSaga(){
    // all 은 배열안에 있는 모든 제너레이터 함수들이 병행적으로 동시에 실행되고, 전부 이행될 때까지 기다림.
    // Promise.all 과 비슷하다고 해서 찾아보니 모든 것들이 이행될 때까지 기다리고 하나라도 에러가 나면 모든 Promise는 무시가 되고, catch 문 실행한다니 이와 비슷할듯
    yield all([authSaga(),userSaga()]);
}

export default rootReducer;

user, userSaga 추가해주었움.

 

 

모듈을 또 작성했으니까 회원가입 폼에서 성공 후에 check 를 호출해서 로그인 상태가 되는 과정 추가 구현 해주기

 

src/containers/auth/RegisterForm

 

// useEffect import 
import React,{useEffect} from 'react';

import { useDispatch, useSelector } from 'react-redux';
// auth module register import
import { changeField, initializeForm,register } from '../../modules/auth';

import AuthForm from '../../components/auth/AuthForm';

import { check } from '../../modules/user'

const RegisterForm = () => {
    const dispatch = useDispatch();
    // user add
    const { form, auth, authError,user } = useSelector(({ auth,user }) => ({
      form: auth.register,
      auth: auth.auth,
      authError: auth.authError,
    //   add
      user: user.user
     
    }));
    // input change event 로 액션 디스패치. 디스패치 : 액션을 발생시키는 것.
    const onChange = e => {
        const { value, name } = e.target;
        dispatch(
          changeField({
            form: 'register',
            key: name,
            value,
          }),
        );
      };
    // form submit event 로 register 함수에 현재 username, password 를 파라미터로 넣어서 액션 디스패치. 
    // 사가에 대한 API 요청 처리후, 이에 대한 결과를 auth/authError 를 통해 조회
    const onSubmit = e => {
        e.preventDefault();
        const { username, password, passwordConfirm } = form;
        console.log(e);
        
        
        if (password !== passwordConfirm){
            // 오류처리 할 것임
            return ; 
        }
        dispatch(register({ username, password }));
    };


    // 컴포넌트 초기 렌더링시 form 초기화
    useEffect(()=>{
        // 액션 생성 함수 import
        dispatch(initializeForm('register'));
    },[dispatch]);

    // auth, authError 값 중에서 무엇이 유효한지에 따라 다른 작업을 함.
    useEffect(()=>{
        if (authError){
            console.log('오류 발생');
            console.log(authError);
            return;
        }
        if(auth){
            console.log('회원가입 성공');
            console.log(auth);
            // 회원가입이 성공하면 check 액션 디스패치.
            dispatch(check());
        }
    },[auth,authError,dispatch])
    // user check API 확인
    useEffect(()=>{
        if(user){
            console.log('check API 성공');
            console.log(user)
        }
    },[user])

    return (
        
       <AuthForm
       type="register"
       form={form}
       onChange={onChange}
       onSubmit={onSubmit} 
       />
    );
};

export default RegisterForm;

 

계속 한 단계씩 쌓아나가니까... 조금 그래도 어디어디를 추가해야할지 같은것도 좀 더..보이길 바라며..! 그리고 미래의 나를 위해 과정을 하나하나 적어두겠숴이

 

 

이제 실행..해볼까!!!!!

 

 

실행화면

http://localhost:3000/register

 

이렇게 checkAPI 성공도 뜨고!!!!!

 

리덕스에서 user 값이 잘들어가 있으면!!!! 성공이다~!!!!! 으아..후 이건 다행히 오래걸리진 않았어 흐그흐그극

 

 

이제 회원가입에 성공헀다면 홈화면으로 가기를 만져보자 경로 관련된건 라우터겠즤!?!?

 

 

src/containers/auth/RegisterForm

 

경로 적다가 시간 다갈듯

ㅋㅋㅋㅋㅋ하지만 계속 경로를 적는 이유는 제일 헷갈리기도 하고 이정도면 나도 오기로 적는다 ㅋㅋㅋㅋㅋㅋ 그래도...난 다적을거야...내가헷갈리거든..... 쨋든 경로 이동을 하기 위해서 history 객체를 쓸 텐데 얘를 통해서 컴포넌트 내에 구현하는 메소드에서 라우터에 직접 접근할 수 있다! 뒤로가기 특정 경로로 이동 이탈 방지 등등~~~ 그리고 라우트가 아닌 컴포넌트에서 얘를 사용할 때는 사진처럼 withRouter 라는 것을 불러와서 컴포넌트를 감싸주면 된다!

 

 

 

 

이렇게 컴포넌트를 감싸주고 

 

 

 

유저 값을 체크하는데에다가 넣어주자. 유저의 값이 정상적으로 들어왔다면 콘솔 로그와 함께 홈화면으로 이동할 것.

 

 

이렇게 잘갔을랑가!!!! 쿄쿄

 

 

 

이제 로그인!!!! 구현하자!!!! 얼마 안남았다!!!! 지금 새벽 3시 30분!!!!!

 

 

src/containers/auth/LoginForm

// useEffect import 
import React,{useEffect} from 'react';
// useSelector, useDispatch import
import { useDispatch, useSelector } from 'react-redux';
// auth module 액션 생성함수, login import 
import { changeField, initializeForm,login } from '../../modules/auth';
// AuthForm import
import AuthForm from '../../components/auth/AuthForm';

//withRouter import
import { withRouter } from 'react-router-dom';
// user import
import { check } from '../../modules/user';

const LoginForm = ({history}) => {
    const dispatch = useDispatch();
    const { form,auth, authError, user } = useSelector( ({ auth,user }) => ( {
        form : auth.login,
        auth:auth.auth,
        authError: auth.authError,
        user:user.user
    }));
    // input change event 로 액션 디스패치. 디스패치 : 액션을 발생시키는 것.
    const onChange = e => {
        const { value, name } = e.target;
        // 액션 생성 함수 import
        dispatch(
            changeField({
                form:'login',
                key:name,
                value
            })
        );
    };
    // form submit event
    // form submit event 로 login 함수에 현재 username, password 를 파라미터로 넣어서 액션 디스패치. 
    // 사가에 대한 API 요청 처리후, 이에 대한 결과를 auth/authError 를 통해 조회
    const onSubmit = e => {
        e.preventDefault();
        const { username, password } = form ;
        // login 함수 디스패치
        dispatch(login({username, password}));
    }
    // 컴포넌트 초기 렌더링시 form 초기화
    useEffect(()=>{
        // 액션 생성 함수 import
        dispatch(initializeForm('login'));
    },[dispatch]);

    useEffect(()=>{
        if (authError){
            console.log('오류 발생');
            console.log(authError);
            return ;
        }
        if (auth){
            console.log('로그인 성공')
            // login 성공하면 check 디스패치
            dispatch(check());
        }
    },[auth,authError, dispatch])


    useEffect(()=>{
        if (user){
            // user 값이 제대로 들어갔으면 인덱스 페이지로
            history.push('/');
        }
    },[history,user]);



    return (
        
       <AuthForm
       type="login"
       form={form}
       onChange={onChange}
       onSubmit={onSubmit} 
       />
    );
};

// withRouter 로 컴포넌트 감싸기
export default withRouter(LoginForm);

 

registerform 이랑 거ㅓㅓ의 유사..! 주석으로 다시 정리한거 보면서 흐름 익히는중.. 

 

실행해보잡.

 

실행 화면

http://localhost:3000/login

 

방금 회원가입한 계정으로 로그인 버튼 누르면

 

이렇게 콘솔과 함께 인덱스 화면으로 가면 성공!!! 

 

 


 

 

으으.... 아직 안끝났어...아직이야!!!! 사실 내가 책이랑 거진 똑같이 + 구글링으로 흐름 잡아가서 그렇지 아직 중요한 에러처리가 남았다. 얘는 보고 좀 더 정리해볼 예정 ....자세한 맥락은 내가 보기에 좋으라고 한거지 사실 좋은 포스팅은 아닌거같기에..껄껄

 

이제 요청 실패했을 때 에러처리 하러 꼬!

 

src/components/auth/AuthForm

 

에러 메세지 스타일 부터 만들어줘야겠디

 

 

 

그러고 error 도 props 로 넘겨서 로그인폼이랑 회원가입폼에서 얘를 세팅하러 가자! 로그인이 좀 더 안까다로우니까 이번엔 로그인부터!

 

 

 

왕 useState 오랜만에 보니까 몹ㅂ ㅂ ㅂ ㅂ ㅂ 시 반가워..... 반가워ㅠㅠㅠㅠ

 

그냥 초기값은 null 해줬다가 

 

 

에러가 나는 시점에서 에러값을 세터함수로 넣어주면 된다!

 

 

 

그러고 props 값도 잊지말고 넣어주자

 

 

좋아 지금까지 한 것중에 제일 무난했다. 잘못된 정보를 넣으면 바로 에러메세지가 뜬당

 

 

 

하지만 회원가입은 매우 까다롭군....흑흑 빠르게 가보자

 

먼저 어떠한 상황의 에러를 처리할 것이냐

 

username,password,passwordConfirm 중 하나라도 비어있는 값이 있는 경우

password, passwordConfirm 값이 일치하지 않을 때

username 이 중복될 때 

 

// useEffect import 
import React,{useEffect ,useState } from 'react';

import { useDispatch, useSelector } from 'react-redux';
// auth module register import
import { changeField, initializeForm,register } from '../../modules/auth';

import AuthForm from '../../components/auth/AuthForm';

import { check } from '../../modules/user'

//withRouter import
import { withRouter } from 'react-router-dom';

const RegisterForm = ({history}) => {
  // error props null setting
    const [ error , setError ] = useState(null);
    const dispatch = useDispatch();
    // user add
    const { form, auth, authError, user } = useSelector(({ auth,user }) => ({
      form: auth.register,
      auth: auth.auth,
      authError: auth.authError,
    //   add
      user: user.user,
     
    }));
    // input change event 로 액션 디스패치. 디스패치 : 액션을 발생시키는 것.
    const onChange = e => {
        const { value, name } = e.target;
        dispatch(
          changeField({
            form: 'register',
            key: name,
            value,
          }),
        );
      };
    // form submit event 로 register 함수에 현재 username, password 를 파라미터로 넣어서 액션 디스패치. 
    // 사가에 대한 API 요청 처리후, 이에 대한 결과를 auth/authError 를 통해 조회
    const onSubmit = e => {
        e.preventDefault();
        // form 입력시 에러 처리
        const { username, password, passwordConfirm } = form;
        // 값이 하나라도 비어있다면
        if ([username, password, passwordConfirm].includes('')){
          setError('빈 칸을 모두 입력하세요.');
          return;
        }
        // 비밀번호가 일치하지 않는다면
      if (password !== passwordConfirm) {
        setError('비밀번호가 일치하지 않습니다.');
        // 에러를 발생시키고 비밀번호, 비밀번호 확인 인풋 공백 (초기화) 처리
        dispatch(changeField({ form: 'register', key: 'password', value: '' }));
        dispatch(
          changeField({ form: 'register', key: 'passwordConfirm', value: '' }),
        );
        return;
      }
      dispatch(register({ username, password }));
    };



    // 컴포넌트 초기 렌더링시 form 초기화
    useEffect(()=>{
        // 액션 생성 함수 import
        dispatch(initializeForm('register'));
    },[dispatch]);

    // auth, authError 값 중에서 무엇이 유효한지에 따라 다른 작업을 함.
    // 회원가입 실패
    useEffect(() => {
      if (authError) {
        // 계정명이 이미 존재할 때
        // confict 409
        if (authError.response.status === 409) {
          setError('이미 존재하는 계정명입니다.');
          return;
        }
        // 기타 이유
        setError('회원가입 실패. 다시 시도해주세요.');
        return;
      }

      // 회원가입 성공
      if(auth){
            console.log('회원가입 성공');
            console.log(auth);
            // 회원가입이 성공하면 check 액션 디스패치.
            dispatch(check());
        }
    },[auth,authError,dispatch])
    // user check API 확인
    useEffect(() => {
      if (user) {
        console.log('check API 성공');
        console.log(user);
        history.push('/'); // 홈 화면으로 이동
      }
    }, [history, user]);
    return (
        
       <AuthForm
       type="register"
       form={form}
       onChange={onChange}
       onSubmit={onSubmit} 
      //  error props add
       error={error}
       />
    );
};

export default withRouter(RegisterForm);

그래도 막 어렵게 세팅하는게 아니라 useState 에 에러메세지를 띄우면 되니까 넘 좋다....ㅎ그그ㅡ긓그.. 

 

 

실행 화면

http://localhost:3000/register

 

 

 

다 잘뜨면 넘나링 성공쓰!!!!!! 

 


안되겠따 로그인 유지 부분은 3편으로 또 빼야겠땈ㅋㅋㅋㅋㅋ 휴.... 지금 4시긴한데 추석 당일까지는 절대 미루지 않으리라했는데 ㅂㄷㅂㄷ 일단은 안되면 친척집 가서도 할거야 ㅂㄷㅂㄷ.... 흑흑 알바하고 노느라 소홀했으니 오늘은 밤새도 싸다.... 그리고 이것저것 하는데도 시간 오래 걸리고 점점 정리를 안하려고 하는데 그것도 습관 들이면 안되기 때문에 책이랑 구글링해가면서 최대한으로 주석 겁나 달아서 시간도 더 걸린 것 같다...! 뒷부분은 좀 정리해서 간단하게 포스팅해야지...이러면 내가 좀 더 구체적으로 적었다는 합리화밖에 더 되는 것.... 정리노트가 아님 아님 모르다 보니까 줄줄 적었는데 다음 포스팅엔 기필코 정리노트로!!! 돌아오겠다 그럼 뿅

댓글