import React, {useEffect} from 'react';
import axios from 'axios';
import {useDispatch} from 'react-redux'
import {auth} from '../_actions/user_action'
export default function (SpecificComponent, option, adminRoute = null){
function AuthenticationCheck(props){
const dispatch = useDispatch();
useEffect(() => {
dispatch(auth()).then(response => { //backend에서 처리한 정보들이 response안에 다 들어있음
console.log(response)
})
}, [])
return (
<SpecificComponent/>
)
}
return AuthenticationCheck
}
Step 2) 서버에서는 미들웨어로 이동
서버 쪽에 구현해놓은 API 를 보자.
index.js에 이미 구현해놓았다.
get request로 요청을 받으면 미들웨어 auth가
여기로 그 요청이 넘어온다. 여기서는 토큰을 사용해서 쿠키 안에 토큰을 집어 넣는다. 그 쿠키를 보고 로그인 한 유저인지 아닌지를 판단하고 그 결과를 리액트에 넘겨준다.
Step 1)-1 Action 구현
그리고 user_action.js에도 아래와 같이 인증 기능을 하는 함수를 추가해준다.
export function auth() {
const request = Axios.get('/api/users/auth') //endpoint로 get request , get이니까 login과 다르게 get 이니까 body 파라미터 필요 없음
.then(response => response.data)
return { //Action 했으니까 이제 Reducer로 보냄
type: AUTH_USER,
payload: request
}
}
Step 1)-2 Reducer 구현
action에 따라 결과를 리턴해주는 곳이다. AUTH_USER 케이스를 추가해준다.
import {
LOGIN_USER, REGISTER_USER, AUTH_USER
} from '../_actions/types';
export default function (state={}, action){ //state 는 이전 상태
switch(action.type){ //Action에는 여러 타입 존재함. 이 타입에 따라 다르게 반응하도록 작성
case LOGIN_USER:
return {...state, loginSuccess: action.payload} //... : spread operator은 파라미터 state를 그대로 가져온 것으로 빈 상태를 의미
break;
case REGISTER_USER:
return { ...state, register: action.payload}
break;
case AUTH_USER:
return { ...state, userData: action.payload}
break;
default:
return state;
}
}
Step 4) 컴포넌트를 Auth HOC에 넣기
각 컴포넌트를 Auth로 감싸주고 파라미터를 지정해서 넘겨준다. 파라미터 설명은 Step1) 에 기재했다.
import React from 'react'
import {
BrowserRouter as Router,
Switch,
Route,
// eslint-disable-next-line
Link
} from "react-router-dom";
import LandingPage from './components/views/LandingPage/LandingPage';
import LoginPage from './components/views/LoginPage/LoginPage';
import RegisterPage from './components/views/RegisterPage/RegisterPage';
import Auth from './hoc/auth'
function App() {
return (
<Router>
<div>
{/*
A <Switch> looks through all its children <Route>
elements and renders the first one whose path
matches the current URL. Use a <Switch> any time
you have multiple routes, but you want only one
of them to render at a time
*/}
{ /* 로그인한 유저는 로그인 페이지에 못들어감 */}
<Switch>
<Route exact path="/" component={Auth(LandingPage, null)} />
<Route exact path="/login" component={Auth(LoginPage, false)} />
<Route exact path="/register" component={Auth(RegisterPage, false)} />
</Switch>
</div>
</Router>
);
}
export default App;
Step 5) 기능 확인하기
이제 실행해서 결과를 확인해보자.
1) 로그인 전 LandingPage
2)로그인 페이지
isAuth 가 false이다.
3) 로그인 후 LandingPage
3) 로그아웃 후 로그인 페이지
Step 6) 페이지 접근 관리하기
위에 있는 payload.isAuth는 로그인 된 상태이고 아래 있는 payload.isAuth는 로그아웃된 상태이다. 각각 true, false 이므로 이에 맞게 코드를 작성하자.
auth.js를 다음과 같이 수정해서 로그인 한 사람은 로그인 페이지나 회원가입 페이지에 접근하지 못하도록 하는 등 페이지 접근을 관리한다.
import React, {useEffect} from 'react';
import axios from 'axios';
import {useDispatch} from 'react-redux'
import {auth} from '../_actions/user_action'
export default function (SpecificComponent, option, adminRoute = null){
function AuthenticationCheck(props){
const dispatch = useDispatch();
useEffect(() => {
//auth는 action이름
dispatch(auth()).then(response => { //backend에서 처리한 정보들이 response안에 다 들어있음
console.log(response)
//로그인 하지 않은 상태인데
if(!response.payload.isAuth) { //option이 true이면 즉 로그인 된 유저만 들어갈 수 있어야하면
if(option){
props.history.push('/login')
}
} else{ //로그인 된 상태인데
if(adminRoute && !response.payload.isAdmin){
props.history.push('/')
}
else{
if(option===false){ //option이 false면 로그인한 유저는 출입 불가능함. 로그인 페이지 or 회원가입 페이지
props.history.push('/')
}
}
}
})
}, [])
return (
<SpecificComponent/>
)
}
return AuthenticationCheck
}
참고)
Page 이동에 사용되는 history는 react-router-dom을 이용해서 쓰고 있는 것임.
withRouter 로
export default withRouter(LandingPage)
코드 맨 아래에 위와 같이 처리를 해줘야 오류가 안 난다.
여기까지 리엑트 노드를 이용한 로그인, 로그아웃, 회원가입 페이지 구현을 해 보았다.
직접 코드를 작성하면서 웹사이트 구현을 위한 기초를 다질 수 있었고, 이를 통해 서버 및 클라이언트의 전체적인 동작 원리 및 흐름과 MongoDB 사용법 또한 익힐 수 있었다.
import Axios from 'axios';
import {
LOGIN_USER, REGISTER_USER
} from './types';
export function loginUser(dataTosubmit) {
const request = Axios.post('/api/users/login', dataTosubmit) //서버에 리퀘스트 날리고
.then(response => response.data) //받은 데이터를 request에 저장
return { //Action 했으니까 이제 Reducer로 보냄
type: LOGIN_USER,
payload: request
}
}
export function registerUser(dataTosubmit) {
const request = Axios.post('/api/users/register', dataTosubmit) //서버에 리퀘스트 날리고
.then(response => response.data) //받은 데이터를 request에 저장
return { //Action 했으니까 이제 Reducer로 보냄
type: REGISTER_USER,
payload: request
}
}
Step 3) type 지정
//action의 타입들만 관리하는 파일
export const LOGIN_USER = "login_user";
export const REGISTER_USER = "register_user";
Step 4) Reducer 작성
user_reducer.js
import { LOGIN_USER, REGISTER_USER } from '../_actions/types';
export default function(state={}, action){ //state 는 이전 상태
switch(action.type){ //Action에는 여러 타입 존재함. 이 타입에 따라 다르게 반응하도록 작성
case LOGIN_USER:
return {...state, loginSuccess: action.payload} //... : spread operator은 파라미터 state를 그대로 가져온 것으로 빈 상태를 의미
break;
case REGISTER_USER:
return { ... state, register: action.payload}
break;
default:
return state;
}
}
import React, {useEffect} from 'react'
import axios from 'axios';
function LandingPage(){//Functional Component 만들기
useEffect(() => {
axios.get('/api/hello') //endpoint. getRequest를 server 즉 index.js로 보내질 것
.then(response => {console.log(response)}) //server 에서 돌아온 response를 콘솔창에 출력해봄
}, [])
return(
<div style={{
display: 'flex', justifyContent: 'center', alignItems: 'center',
width: '100%', height: '100vh'
}}>
<h2> 시작 페이지 </h2>
</div>
)
}
export default LandingPage
그럼 첫 시작 화면이 아래와 같이 뜬다.
Step 2) 로그인 페이지 작성
onChange에 함수를 지정해줌으로써 이메일이나 비밀번호를 입력할 때, 실시간으로 state가 변하게 해준다.
//client/src/components/views/LoginPage/LoginPage.js
import React, { useState } from 'react'
import Axios from 'axios'
import {useDispatch} from 'react-redux';
import {loginUser} from '../../../_actions/user_action'
function LoginPage(props) { //파라미터로 props 넣어줘야함! 로그인 완료된 후 처음 화면으로 돌아가게 하기 위함
const dispatch = useDispatch();
const [Email, setEmail] = React.useState(" ")
const [Password, setPassword] = React.useState(" ")
const onEmailHandler = (event) => {
setEmail(event.currentTarget.value)
}
const onPasswordHandler = (event) => {
setPassword(event.currentTarget.value)
}
const onSubmitHandler = (event) => {
event.preventDefault(); //리프레시 방지-> 방지해야 이 아래 라인의 코드들 실행 가능
// console.log('Email', Email);
// console.log('Password', Password);
let body={
email: Email,
password: Password
}
//디스패치로 액션 취하기
dispatch(loginUser(body))
.then(response => {
if(response.payload.loginSuccess) {
props.history.push('/') //리액트에서 페이지 이동하기 위해서는 props.history.push() 이용.
// 로그인 완료된 후 처음 화면(루트 페이지-landingpage로)으로 돌악가게 하기
} else{
alert(' Error')
}
})
}
return (
<div style={{
display: 'flex', justifyContent: 'center', alignItems: 'center',
width: '100%', height: '100vh'
}}>
<form style={{display: 'flex', flexDirection: 'column'}}
onSubmit={onSubmitHandler}
>
<label>Email</label>
<input type="email" value={Email} onChange={onEmailHandler}/>
<label>Password</label>
<input type="password" value={Password} onChange={onPasswordHandler}/>
<br />
<button>
Login
</button>
</form>
</div>
)
}
export default LoginPage
Step 3) Action 기능 구현
Login버튼을 누르면 디스패치가 action을 수행할 수 있도록 기능을 구현한다. 그 기능들은 _action/user_action.js 를 새로 생성해서 아래와 같이 코드를 작성하자.
- Server 에 보낼 때 Axios.post() 를 이용한다.
//client/src/_actions/user_action.js
import Axios from 'axios';
import {
LOGIN_USER
} from './types'
export function loginUser(dataTosubmit) {
const request = Axios.post('/api/users/login', dataTosubmit) //서버에 리퀘스트 날리고
.then(response => response.data) //받은 데이터를 request에 저장
return { //Action 했으니까 이제 Reducer로 보냄
type: LOGIN_USER,
payload: request
}
}
Step 4) Reducer 기능 구현
이 다음은 Reducer 차례로, (previousState, action) 을 받아서 nextState를 리턴해주는 역할을 할것이다.
Action의 여러가지 type들을 쉽게 관리하기 위해 client/src/_actions/types.js파일을 새로 만들어서 아래와 같이 코드를 작성한다.
//action의 타입들만 관리하는 파일
export const LOGIN_USER = "login_user";
그리고 client/src/_reducers/user_reducer.js 파일도 아래와 같이 작성해준다.
import { LOGIN_USER } from '../_actions/types';
export default function(state={}, action){ //state 는 이전 상태
switch(action.type){ //Action에는 여러 타입 존재함. 이 타입에 따라 다르게 반응하도록 작성
case LOGIN_USER:
return {...state, loginSuccess: action.payload} //... : spread operator은 파라미터 state를 그대로 가져온 것으로 빈 상태를 의미
break;
default:
return state;
}
}