로그인 화면 구현
이메일과 패스워드를 입력받아서 로그인을 하는 화면을 만든다.
회원가입과 마찬가지로 formik과 yup 라이브러리를 사용해서 validation을 처리한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
import React from 'react'; import {Formik} from "formik"; import * as Yup from "yup"; import {Button, Col, Form, Row} from "react-bootstrap"; import axios from "axios"; import {toast} from "react-toastify"; const Login = (props: any) => { const submit = async (values: any) => { const {email, password} = values; try { const {data} = await axios.post('/api/auth/signin', {email, password}); console.log(data); toast.success('로그인하였습니다.', { position: "top-center", autoClose: 3000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: true, progress: undefined, }); props.history.push('/'); } catch(e) { console.log(e.toString()); toast.error('로그인에 실패하였습니다. 다시 시도하세요', { position: "top-center", autoClose: 3000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: true, progress: undefined, }); } } return ( <> <div className="d-flex justify-content-center my-3"> <h2>로그인</h2> </div> <Row className="justify-content-center"> <Col xs={12} sm={10} md={8} lg={6} xl={4}> <Formik initialValues={{email: '', password: ''}} onSubmit={submit} validationSchema={Yup.object().shape({ email: Yup.string() .email("이메일형식으로 입력하세요") .required("필수필드 입니다."), password: Yup.string() .required("필수필드 입니다.") })}> { ({ values, errors, touched, handleChange, handleBlur, handleSubmit, isSubmitting }) => (<Form onSubmit={handleSubmit}> <Form.Group controlId="email"> <Form.Label>Email address</Form.Label> <Form.Control name="email" placeholder="Enter email" value={values.email} onChange={handleChange} onBlur={handleBlur} isValid={touched.email && !errors.email} isInvalid={touched.email && errors.email ? true : false}/> {touched.email && !errors.email && <Form.Control.Feedback type="valid">Looks good!</Form.Control.Feedback>} {touched.email && errors.email && <Form.Control.Feedback type="invalid">{errors.email}</Form.Control.Feedback>} </Form.Group> <Form.Group controlId="formGroupPassword"> <Form.Label>Password</Form.Label> <Form.Control type="password" name="password" placeholder="enter Password" value={values.password} onChange={handleChange} onBlur={handleBlur} isValid={touched.password && !errors.password} isInvalid={touched.password && errors.password ? true : false}/> {touched.password && !errors.password && <Form.Control.Feedback type="valid">Looks good!</Form.Control.Feedback>} {touched.password && errors.password && <Form.Control.Feedback type="invalid">{errors.password}</Form.Control.Feedback>} </Form.Group> <Button variant="primary" type="submit" disabled={isSubmitting}> Submit </Button> </Form>) } </Formik> </Col> </Row> </> ); } export default Login; |
jwt 토큰 정보 저장
로그인을 성공하면 서버에서 jwt 토큰을 받는다. 그것을 store에 저장하고 redux-persist 모듈을 사용해서 storage에도 저장한다.
redux에 저장하는것은 영구적인 저장소가 아니다. 만일 새로고침하게 되면 store에 저장된 정보는 메모리에 저장된것이기 때문에 모두 없어지게 된다. 따라서 영구적으로 저장하기 위해서는 storage에 저장해야 하는데 redux-persistence 모듈이 그것을 도와준다.
먼저 필요한 모듈을 설치한다. redux-persist가 local storage에 저장하는 모듈이다. jwt-decode는 jwt를 파싱하는데 도움을 주는 라이브러리이다. querystring 은 query parameter를 쉽게 파싱할 수 있도록 도와주는 모듈이다.
1 |
yarn add redux react-redux redux-persist jwt-decode querystring |
redux 폴더에 AuthReducer를 먼저 생성한다.
src/redux/reducers/AuthReducer.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
const SET_TOKEN = 'set_token'; const AuthInitialState = { token: null } export const setToken = (token) => ({ type: SET_TOKEN, token }) export const AuthReducer = (state = AuthInitialState, action) => { switch(action.type) { case SET_TOKEN: return { ...state, token: action.token } default: return state; } } |
모든 reducer를 combine하는 allReducers를 생성한다.
src/redux/reducers/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import {persistReducer} from "redux-persist"; import storage from "redux-persist/lib/storage"; import {AuthReducer} from "./AuthReducer"; import {combineReducers} from "redux"; const persistConfig = { key: "root", // localStorage에 저장합니다. storage, // auth, board, studio 3개의 reducer 중에 auth reducer만 localstorage에 저장합니다. // whitelist: ["auth"] // blacklist -> 그것만 제외합니다 }; const allReducers = combineReducers({ Auth: AuthReducer }); export default persistReducer(persistConfig, allReducers); |
store를 생성한다.
src/redux/store.js
1 2 3 4 |
import {createStore} from "redux"; import allReducers from "./reducers"; export const store = createStore(allReducers, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
const Login = (props: any) => { console.log(props); const dispatch = useDispatch(); const submit = async (values: any) => { const {email, password} = values; try { const {data} = await axios.post('/api/auth/signin', {email, password}); console.log(data); dispatch(setToken(data.token)) const {redirectUrl} = queryString.parse(props.location.search); if (redirectUrl) { props.history.push(redirectUrl); } else { props.history.push('/'); } toast.success('로그인하였습니다.', { position: "top-center", autoClose: 3000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: true, progress: undefined, }); props.history.push('/'); } catch(e) { console.log(e.toString()); toast.error('로그인에 실패하였습니다. 다시 시도하세요', { position: "top-center", autoClose: 3000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: true, progress: undefined, }); } } ... } |