useContext 및 useReducer를 사용하여 사용자 로그인 인증하기
npx create-react-app login-auth
yarn add react-router-dom
yarn add axios
// pages/login/index.js
import React from 'react';
import styles from './login.module.css';
function Login(props) {
return (
<div className={styles.container}>
<div className={styles.formContainer}>
<h1>Login Page</h1>
<form>
<div className={styles.loginForm}>
<div className={styles.loginFormItem}>
<label htmlFor='email'>Username</label>
<input type='text' id='email' />
</div>
<div className={styles.loginFormItem}>
<label htmlFor='password'>Password</label>
<input type='password' id='password' />
</div>
</div>
<button>login</button>
</form>
</div>
</div>
);
}
export default Login;
// pages/login/login.module.css
.container {
min-height: 100vh;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.formContainer {
width: 200px;
}
.error {
font-size: 0.8rem;
color: #bb0000;
}
.loginForm {
display: flex;
flex-direction: column;
}
.loginFormItem {
display: flex;
flex-direction: column;
margin-bottom: 10px;
}
// pages/dashboard/index.js
import React from 'react'
import styles from './Dashboard.module.css'
function Dashboard(props) {
return (
<div style={{ padding: 10 }}>
<div className={styles.dashboardPage} >
<h1>
Dashboard
</h1>
<button className={styles.logoutBtn} >Logout</button>
</div>
<p>Welcome to the dashboard</p>
</div>
)
}
export default Dashboard
// pages/dashboard/dashboard.module.css
.logoutBtn {
height: '30px';
width: '100px';
}
.dashboardPage {
display: flex;
width: 100%;
justify-content: space-between;
}
// pages/notFound/index.js
import React from 'react';
import styles from './notfound.module.css';
function NotFound(props) {
return (
<div className={styles.container}>
<h1>Page not found</h1>
</div>
);
}
export default NotFound;
// pages/notFound/notfound.module.css
.container {
min-height: 100vh;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
// Config/routes.js
import React from 'react'
import Login from '../pages/login/index'
import DashBoard from '../pages/dashBoard/index'
import PageNotFound from '../pages/pageNotFound/index'
const routes =[
{
path:'/',
component: Login
},
{
path:'/dashboard',
component: Dashboard
},
{
path:'/*',
component: PageNotFound
},
]
export default routes
// App.js
import React from 'react';
import {
BrowserRouter as Router,
Redirect,
Route,
Switch,
} from 'react-router-dom';
import routes from './Config/routes.js';
function App() {
return (
<Router>
<Switch>
{routes.map((route) => (
<Route
exact
key={route.path}
path={route.path}
component={route.component}
/>
))}
</Switch>
</Router>
);
}
export default App;
dispatch
를 전달한다 . 이렇게 하면 필요한 구성 요소에 dispatch
를 쉽게 제공할 수 있습니다.컨텍스트 객체를 생성하기 위해 context.js파일에 아래를 추가 한다.
// Context/context.js
import React,{useContext,createContext,useReducer} from "react";
import {AuthReducer,initialState} from './reducer'
const AuthStateContext = createContext(null)
const AuthDispatchContext = createContext(null)
동일한 파일(context.js)에서 useAuthDispatch
,useAuthDispatch
hooks, provider를 생성 한다 .
// Context/context.js
[...]
export function useAuthState() {
const context = React.useContext(AuthStateContext);
if (context === undefined) {
throw new Error("useAuthState는 AuthProvider 안에서만 사용 가능합니다.")
}
return context;
}
export function useAuthDispatch() {
const context = React.useContext(AuthDispatchContext);
if (context === undefined) {
throw new Error("useAuthDispatch는 AuthProvider 안에서만 사용 가능합니다.")
}
return context;
}
export const AuthProvider =({children})=>{
const [user,dispatch] = useReducer(AuthReducer,initialState)
return(
<AuthStateContext.Provider value={user}>
<AuthDispatchContext.Provider value={dispatch}>
{children}
</AuthDispatchContext.Provider>
</AuthStateContext.Provider>
)
}
이렇게만 하면 reducer를 만들기 않았기 때문에 에러가 난다. reducer,action을 만들어보자
// Context/reducer.js
let user = localStorage.getItem('currentUser')? JSON.parse(localStorage.getItem('currentUser')).user : '';
let token = localStorage.getItem('currentUser')? JSON.parse(localStorage.getItem('currentUser')).auth_token : '';
export const initialState ={
user:""||user,
token:""||token,
loading:false,
errorMessage:null
}
export const AuthReducer =(initialState,action)=>{
switch (action.type){
case 'REQUEST_LOGIN':
return{
...initialState,
loading: true
}
case 'LOGIN_SUCCESS':
return{
...initialState,
user:action.payload.user,
token:action.payload.auth_token,
loading: false
}
case 'LOGOUT':
return{
...initialState,
user:'',
token:''
}
case 'LOGIN_ERROR':
return{
...initialState,
loading: false,
errorMessage: action.error
}
default:
throw new Error( `Unhandled action type: ${action.type}`)
}
}
// Context/action.js
import axios from "axios";
const ROOT_URL = 'https://secret-hamlet-03431.herokuapp.com';
export const loginUser=async (dispatch,loginPayload)=>{
const requestOptions={
url:`${ROOT_URL}/login`,
method: 'POST',
headers: {
'Content-Type':'application/json'
},
data:loginPayload
}
try{
const response = await axios(requestOptions)
if(response.status ===200){
dispatch({type:'LOGIN_SUCCESS',payload:response.data})
localStorage.setItem('currentUser',JSON.stringify(response.data))
return response.data
}else{
dispatch({type:'LOGIN_ERROR',error:response.data.error[0]})
}
return ;
}catch (e){
dispatch({type:'LOGIN_ERROR',error:e})
}
}
export async function logout(dispatch) {
dispatch({ type: 'LOGOUT' });
localStorage.removeItem('currentUser');
localStorage.removeItem('token');
}
이제 useReducer를 사용하여 컨텍스트 및 상태 관리에 대한 모든 설정을 완료 했으고, Context폴더의 모든 내용을 내보낼 폴더에 index.js 파일을 생성한다.
import {loginUser,logout,axiosLoginUser} from './actions'
import {AuthProvider,useAuthState,useAuthDispath} from './context'
export {loginUser,logout,useAuthDispath,useAuthState,AuthProvider,axiosLoginUser}
// App.js
import routes from './Config/routes.js';
import { AuthProvider } from "./Context";
function App() {
return (
<AuthProvider>
<Router>
<Switch>
{routes.map((route) => (
<Route
exact
key={route.path}
path={route.path}
component={route.component}
/>
))}
</Switch>
</Router>
</AuthProvider>
);
}
export default App;
이메일과 비밀번호 입력 필드의 상태와 입력 핸들러를 정의한다.
// pages/login/index.js
[...]
function Login(props) {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
return (
<div className={styles.container}>
<div className={{ width: 200 }}>
<h1>Login Page</h1>
<form >
<div className={styles.loginForm}>
<div className={styles.loginFormItem}>
<label htmlFor="email">Username</label>
<input type="text" id='email' value={email} onChange={(e) => setEmail(e.target.value)} />
</div>
<div className={styles.loginFormItem}>
<label htmlFor="password">Password</label>
<input type="password" id='password' value={password} onChange={(e) => setPassword(e.target.value)} />
</div>
</div>
<button>login</button>
</form>
</div>
</div>
)
}
[...]
이 시점에서 상태에서 사용할 수 있는 이메일 및 비밀번호 필드가 있어 이 정보들을 서버에 제출하는 것을 처리하는 함수를 만들 수 있다.
handleLogin
함수를 만들어 호출하고, login
버튼 을 클릭하면 호출된다.
로그인 구성 요소에서 handleLogin함수를 만듭니다 .
// pages/login/index.js
[...]
import {useAuthState,useAuthDispath,axiosLoginUser} from '../../context'
[...]
function Login(props) {
const dispatch = useAuthDispath()
const [email,setEmail] =useState('')
const [password,setPassword] =useState('')
const {loading} = useAuthState() //얘는 initialState안에 있는 얘들 구조분해 할당
const handleLogin =async (e)=>{
e.preventDefault()
let payload = {email,password}
try{
const response = await axiosLoginUser(dispatch,payload)
if(!response.user) return
props.history.push('/dashboard')
}catch (e){
console.error(e)
}
}
return (
<div className={styles.container}>
<div className={styles.formContainer}>
<h1>Login Page</h1>
<form >
<div className={styles.loginForm}>
<div className={styles.loginFormItem}>
<label htmlFor="email">UserName</label>
<input type="text" id={'email'} value={email} onChange={(e)=>setEmail(e.target.value)} disabled={loading}/>
</div>
<div className={styles.loginFormItem}>
<label htmlFor="password">Password</label>
<input type="password" id={'password'} value={password} onChange={(e)=>setPassword(e.target.value)} disabled={loading}/>
</div>
</div>
<button onClick={handleLogin}>login</button>
</form>
</div>
</div>
);
}
export default Login;
로그인 테스트 email: nero@admin.com
,
로그인 테스트 password: admin123
// pages/dashboard/index.js
import React from 'react'
import styles from './dashBoard.module.css'
import {useAuthState,logout,useAuthDispath} from "../../context";
function Dashboard(props) {
const dispatch = useAuthDispath()
const userDetails = useAuthState() // 얘는 initialState
const handleLogout=()=>{
logout(dispatch)
props.history.push('/login')
}
return (
<div style={{ padding: 10 }}>
<div className={styles.dashboardPage} >
<h1>
Dashboard
</h1>
<button className={styles.logoutBtn} onClick={handleLogout}>Logout</button>
</div>
<p>Welcome {userDetails.user.email}</p>
</div>
)
}
export default Dashboard
이렇게하면 간단하게 로그인,로그아웃을 만들수 있지만, 이제 인증이 있음에도 불구하고 사용자가 인증되지 않은 경우에도 대시보드 경로와 같은 경로에 계속 액세스할 수 있다는 문제가 있다.
이 문제를 해결하려면 개인 경로(인증된 사용자만 액세스할 수 있는 경로)를 정의하고, 사용자가 인증된 경우 적절한 구성 요소를 렌더링하는 상위 구성 요소를 생성해야 한다. 그렇지 않은 경우 로그인 페이지로 리디렉션된다.
먼저 경로 구성에서 isPrivate경로가 비공개인지 여부를 지정 하는 속성을 추가하려고 한다.
우리 애플리케이션의 두 개인 경로는 대시보드 페이지와 404 페이지이다.
// Config/routes.js
[…]
const routes = [
{
path: '/login',
component: Login,
isPrivate: false,
},
{
path: '/dashboard',
component: Dashboard,
isPrivate: true,
},
{
path: '/*',
component: NotFound,
isPrivate: true,
},
];
export default routes;
다음으로 보호된 경로에 도움이 되는 component를 만든다. components 폴더를 만들고 AppRoutes.js
파일을 생성해 보자 .
그런 다음 AppRoute구성 요소에 다음을 추가할 수 있다.
// components/AppRoute.js
import React from 'react';
import {Redirect,Route} from 'react-router-dom'
import { useAuthState} from "../context";
function AppRoute({component:Component,path,isPrivate,...rest}) {
const userDetails = useAuthState()
return (
<Route
exact
path={path}
render={props =>
isPrivate && !Boolean(userDetails.token) ? (
<Redirect to={{pathname: '/'}}/>
) : (
<Component {...props}/>
)
}
{...rest}
/>
);
}
export default AppRoute;
// App.js
function App() {
return (
<AuthProvider>
<Router>
<Switch>
{routes.map((route) => (
<AppRoute
exact
key={route.path}
path={route.path}
component={route.component}
isPrivate={route.isPrivate}
/>
))}
</Switch>
</Router>
</AuthProvider>
);
}
export default AppRoute;
그리고 이제 실행해주고 테스트하면 된다.
I like looking through a post that will make people think. Also, thank you for allowing me to comment!
https://infocampus.co.in/ui-development-training-in-bangalore.html
https://infocampus.co.in/web-development-training-in-bangalore.html
https://infocampus.co.in/mern-stack-training-in-bangalore.html
https://infocampus.co.in/reactjs-training-in-marathahalli-bangalore.html
https://infocampus.co.in/javascript-jquery-training-in-bangalore.html
I find it interesting when an article challenges my perspective. This one made me think, and I like that it's not just surface-level content. Well done!
https://www.shinebrightx.com/project-management/pmp-certification-training
https://www.shinebrightx.com/project-management/capm--certification-training
https://www.shinebrightx.com/project-management/project-management-techniques
https://www.shinebrightx.com/soft-skill-training/conflict-management-training
https://www.shinebrightx.com/cyber-security/cisa-certification-training
https://www.shinebrightx.com/cyber-security/cism-certification-training
https://www.shinebrightx.com/corporate-training
https://www.shinebrightx.com/project-management/change-management-certification
https://www.shinebrightx.com/it-service-management/itil-foundation-training
https://www.shinebrightx.com/agile-management/csm-certification-training
https://www.shinebrightx.com/quality-management/lean-six-sigma-green-belt
https://www.shinebrightx.com/cyber-security/cissp-certification-training
useContext, useReduce를 통해 사용자 데이터를 관리하는 방법을 찾고 있었는데, 잘 정리해주셔서 감사합니다 ㅎㅎ