我正在使用useContext,useReducer和用于对路由进行身份验证的高阶组件(HOC)来对React项目进行身份验证。
我面临的挑战是,登录后,HOC会重定向到登录页面,因为即使在进入HOC之前将其设置为true,未认证也会返回false。
withAuth 我用来检查用户是否已通过身份验证并被允许访问某些路由的HOC
import React from 'react';
import { Redirect } from 'react-router-dom';
import { verifyRole } from '../../helpers/auth.helpers';
import withUser from '../WithUser';
const withAuth = (...permittedRoles) => (ComposedComponent) => {
const Authorization = (props) => {
const { isAuthenticated } = props;
if (!isAuthenticated) return <Redirect to='/signin' />;
if (!verifyRole(...permittedRoles)) {
return <Redirect to='/' />;
}
return <ComposedComponent {...props} permittedRoles={permittedRoles} />;
};
return withUser(Authorization);
};
export default withAuth;
withUser 提供要使用的上下文状态的HOC
import React from 'react';
import { UserContext } from '../../context/userContext';
const withUser = (ComposedComponent) => (props) => (
<UserContext.Consumer>
{(user) => <ComposedComponent {...props} {...user} />}
</UserContext.Consumer>
);
export default withUser;
authReducer 我用于更新上下文的reducer
import {
SIGNIN_START,
SIGNIN_FAILURE,
SIGNIN_SUCCESS,
} from '../actions/actionTypes';
const authReducer = (state, action) => {
switch (action.type) {
case SIGNIN_START:
return { ...state, isLoading: true, isAuthenticated: false, errorMsg: null };
case SIGNIN_SUCCESS:
return {
...state,
isLoading: false,
response: action.payload,
errorMsg: null,
isAuthenticated: true,
};
case SIGNIN_FAILURE:
return {
...state,
isLoading: false,
response: action.payload.response,
errorMsg: action.payload.response
? action.payload.response.data.message
: 'Sorry, there was an error, please try again',
isAuthenticated: false,
};
default:
return state;
}
};
export default authReducer;
操作 身份验证操作
import { SIGNIN_START, SIGNIN_FAILURE, SIGNIN_SUCCESS } from './actionTypes';
const signinStart = () => ({
type: SIGNIN_START,
});
const signinSuccess = (payload) => ({
type: SIGNIN_SUCCESS,
payload,
});
const signinFailure = (payload) => ({
type: SIGNIN_FAILURE,
payload,
});
export const signin = (dispatch) => {
dispatch(signinSuccess())
}
const signinUser = async (dispatch, userObject) => {
dispatch(signinStart());
try {
const user = await Axios.post(`${process.env.REACT_APP_BASE_API_URL}/auth/signin`, {
...userObject,
});
if (user) {
const { token } = user.data;
localStorage.setItem('token', token);
dispatch(signinSuccess(token));
return true;
}
} catch (error) {
dispatch(signinFailure(error));
}
};
export default signinUser;
SigninComponent
import React, { useState, useContext } from 'react';
import swal from 'sweetalert';
import ClipLoader from 'react-spinners/ClipLoader';
import FormStyles from '../../styles/FormStyles.module.scss';
import SigninStyles from './Signin.module.scss';
import { UserContext } from '../../context/userContext';
import signinUser from '../../actions/auth.action';
export const Signin = ({history}) => {
const initialState = {
username: '',
password: '',
};
const [signinState, setSigninState] = useState(initialState);
const { dispatch, errorMsg, isLoading } = useContext(UserContext);
const { username, password } = signinState;
const handleInputChange = (event) => {
setSigninState({
...signinState,
[event.target.name]: event.target.value,
});
};
const handleSignin = (e) => {
e.preventDefault();
const signinResult = signinUser(dispatch, { username, password });
if(signinResult) history.push('/');
if(errorMsg) {
swal('Oops!!!', errorMsg, 'error');
}
};
return (
<div className={SigninStyles.formContainer}>
<h2 className={`${FormStyles['form-title']} container`}>Signin</h2>
<form className={SigninStyles.formBody} onSubmit={handleSignin}>
<p className={FormStyles['input-group']}>
<label htmlFor='username'>Username</label>
<input
type='text'
name='username'
id='username'
placeholder='Username'
onChange={handleInputChange}
value={username}
required
/>
</p>
<p className={FormStyles['input-group']}>
<label htmlFor='password'>Password</label>
<input
type='password'
name='password'
id='password'
placeholder='password'
onChange={handleInputChange}
value={password}
required
/>
</p>
<p className={FormStyles['btn-group']}>
<button
className={`${FormStyles['btn']} ${FormStyles['btn-maroon']}`}
>
{!isLoading && 'Add user'}
<ClipLoader loading={isLoading} size='30px' color='#fff' />
</button>
<a href='/forgot-password' className={FormStyles['forgot-password']}>
Forgot password?
</a>
</p>
</form>
</div>
);
};
export default Signin;