PrivateOutlet.js 身份验证后不呈现页面
PrivateOutlet.js doesn't render page after authentication
当我尝试下面的代码时,我被重定向到登录页面,就好像我没有通过身份验证一样。登录后我无法查看关于页面,因为它会将我定向到欢迎页面,因为登录页面中的逻辑(如果 isAuthenticated
导航到欢迎页面)。如果我删除登录页面中的逻辑,我只会卡在登录页面中。为什么我无法查看关于页面?
PrivateOutlet.js;
import React from 'react';
import { Outlet, Navigate } from 'react-router-dom';
const PrivateOutlet = ({ isAuthenticated }) => {
if (isAuthenticated ) {
return <Outlet />
} else {
return <Navigate to='login' /> //Go to login
}
};
export default PrivateOutlet;
已更新 PrivateOutlet.js ;
import React from 'react';
import { connect } from 'react-redux';
import { Outlet, Navigate } from 'react-router-dom';
const PrivateOutlet = ({ isAuthenticated }) => {
return isAuthenticated ? <Outlet /> : <Navigate to='/login' replace />;
};
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated
});
export default connect(mapStateToProps)(PrivateOutlet);
App.js
function App() {
return (
<Provider store={store}>
<Router>
<Layout>
<Routes>
<Route path='/' element={<WelcomePage/>} />
<Route path='/home' element={<Home/>} />
<Route element={<PrivateOutlet/>}>
<Route path='/about' element={<About/>} />
</Route>
<Route path='/contact' element={<Contact/>} />
<Route path='/login' element={<Login/>} />
<Route path='/signup' element={<Signup/>} />
<Route path='/reset-password' element={<ResetPassword/>} />
<Route path='/password/reset/confirm/:uid/:token' element={<ResetPasswordConfirm/>} />
<Route path='/activate/:uid/:token' element={<Activate/>} />
<Route path='*' element={<NotFound/>} />
</Routes>
</Layout>
</Router>
</Provider>
);
}
export default App;
login.js
import React, { useState } from 'react';
import { Link, Navigate } from 'react-router-dom';
import { connect } from 'react-redux';
import { Button } from '@mui/material';
import { login } from '../actions/auth';
import './Login.css';
import { Helmet } from 'react-helmet';
function Login({ login, isAuthenticated }) {
const [formData, setFormData] = useState({
email: '',
password: ''
});
const { email, password } = formData;
const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value});
const onSubmit = e => {
e.preventDefault();
login (email, password)
};
if (isAuthenticated) {
return <Navigate to='/' />
}
return (
<div className='login'>
<Helmet>
<title>Prosperity - Login</title>
<meta
name='description'
content='login page'
/>
</Helmet>
<h1 className='login__title'>Login</h1>
<p className='login__lead'>Login into your Account</p>
<form className='login__form' onSubmit={e => onSubmit(e)}>
<div className='login__form__group'>
<input
className='login__form__input'
type='email'
placeholder='Email *'
name='email'
value={email}
onChange={e => onChange(e)}
required
/>
</div>
<div className='login__form__group'>
<input
className='login__form__input'
type='password'
placeholder='Password *'
name='password'
value={password}
onChange={e => onChange(e)}
minLength='8'
required
/>
</div>
<Button className='login__button__main' type='submit'>Login</Button>
</form>
<p className='link__to__Signup'>
Do not have an account? <Link to='/signup' className='login__link'>Register</Link>
</p>
<p className='link__to__resetPassword'>
Forgot Password? <Link to='/reset-password' className='reset__password__link'>Reset Password</Link>
</p>
</div>
)
};
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated
});
export default connect (mapStateToProps, { login }) (Login);
actions/Auth.js ;
import axios from 'axios';
import { setAlert } from './alert';
import {
LOGIN_SUCCESS,
LOGIN_FAIL,
SIGNUP_SUCCESS,
SIGNUP_FAIL,
ACTIVATION_SUCCESS,
ACTIVATION_FAIL,
USER_LOADED_SUCCESS,
USER_LOADED_FAIL,
AUTHENTICATED_SUCCESS,
AUTHENTICATED_FAIL,
PASSWORD_RESET_SUCCESS,
PASSWORD_RESET_FAIL,
PASSWORD_RESET_CONFIRM_SUCCESS,
PASSWORD_RESET_CONFIRM_FAIL,
LOGOUT
} from './types';
export const checkAuthenticated = () => async dispatch => {
if (localStorage.getItem('access')) {
const config = {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
};
const body = JSON.stringify({ token: localStorage.getItem('access') });
try {
const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/jwt/verify/`, body, config)
if (res.data.code !== 'token_not_valid') {
dispatch({
type: AUTHENTICATED_SUCCESS
});
} else {
dispatch({
type: AUTHENTICATED_FAIL
});
}
} catch (err) {
dispatch({
type: AUTHENTICATED_FAIL
});
}
} else {
dispatch({
type: AUTHENTICATED_FAIL
});
}
};
export const load_user = () => async dispatch => {
if (localStorage.getItem('access')) {
const config = {
headers: {
'Content-Type': 'application/json',
'Authorization': `JWT ${localStorage.getItem('access')}`,
'Accept': 'application/json'
}
};
try {
const res = await axios.get(`${process.env.REACT_APP_API_URL}/auth/users/me/`, config);
dispatch({
type: USER_LOADED_SUCCESS,
payload: res.data
});
}catch (err) {
dispatch({
type: USER_LOADED_FAIL
});
}
} else {
dispatch({
type: USER_LOADED_FAIL
});
}
};
export const login = (email, password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ email, password });
try {
const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/jwt/create/`, body, config);
dispatch({
type: LOGIN_SUCCESS,
payload: res.data
});
dispatch(setAlert('Authenticated successfully', 'success'));
dispatch(load_user());
}catch (err) {
dispatch({
type: LOGIN_FAIL
});
dispatch(setAlert('Error Authenticating', 'error'));
}
};
export const signup = (name, email, password, re_password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ name, email, password, re_password });
try {
const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/users/`, body, config);
dispatch({
type: SIGNUP_SUCCESS,
payload: res.data
});
dispatch(setAlert('Check Your Email to Activate Your Account.', 'warning'));
} catch (err) {
dispatch({
type: SIGNUP_FAIL
})
}
};
export const verify = (uid, token) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ uid, token });
try {
await axios.post(`${process.env.REACT_APP_API_URL}/auth/users/activation/`, body, config);
dispatch({
type: ACTIVATION_SUCCESS,
});
dispatch(setAlert('Account Activated Successfully.', 'success'));
} catch (err) {
dispatch({
type: ACTIVATION_FAIL
})
}
};
//Reset Password
export const reset_password = (email) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ email });
try {
await axios.post (`${process.env.REACT_APP_API_URL}/auth/users/reset_password/`, body, config);
dispatch({
type: PASSWORD_RESET_SUCCESS
});
dispatch(setAlert('Check Your Email to Rest Password.', 'warning'));
} catch (err) {
dispatch({
type: PASSWORD_RESET_FAIL
});
}
};
// Reset Password Confirm
export const reset_password_confirm = (uid, token, new_password, re_new_password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ uid, token, new_password, re_new_password });
try {
await axios.post (`${process.env.REACT_APP_API_URL}/auth/users/reset_password_confirm/`, body, config);
dispatch(setAlert('Password Rest Successful.', 'success'));
dispatch({
type: PASSWORD_RESET_CONFIRM_SUCCESS
});
} catch (err) {
dispatch({
type: PASSWORD_RESET_CONFIRM_FAIL
});
}
};
//Logout
export const logout = () => dispatch => {
dispatch(setAlert('Logout successful.', 'success'));
dispatch({
type: LOGOUT
});
};
reducers/Auth.js ;
import {
LOGIN_SUCCESS,
LOGIN_FAIL,
SIGNUP_SUCCESS,
SIGNUP_FAIL,
ACTIVATION_SUCCESS,
ACTIVATION_FAIL,
USER_LOADED_SUCCESS,
USER_LOADED_FAIL,
AUTHENTICATED_SUCCESS,
AUTHENTICATED_FAIL,
PASSWORD_RESET_SUCCESS,
PASSWORD_RESET_FAIL,
PASSWORD_RESET_CONFIRM_SUCCESS,
PASSWORD_RESET_CONFIRM_FAIL,
LOGOUT
} from '../actions/types';
const initialState = {
access: localStorage.getItem('access'),
refresh: localStorage.getItem('refresh'),
isAuthenticated: null,
user: null,
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch(type) {
case AUTHENTICATED_SUCCESS:
return {
...state,
isAuthenticated: true
}
case LOGIN_SUCCESS:
localStorage.setItem('access', payload.access);
localStorage.setItem('refresh', payload.refresh);
return {
...state,
isAuthenticated: true,
access: payload.access,
refresh: payload.refresh,
}
case USER_LOADED_SUCCESS:
return {
...state,
user: payload
}
case SIGNUP_SUCCESS:
return {
...state,
isAuthenticated: false,
}
case AUTHENTICATED_FAIL:
return {
...state,
isAuthenticated: false
}
case USER_LOADED_FAIL:
return {
...state,
user: null
}
case LOGIN_FAIL:
case SIGNUP_FAIL:
case LOGOUT:
localStorage.removeItem('access');
localStorage.removeItem('refresh');
return {
...state,
access: null,
refresh: null,
isAuthenticated: false,
user: null,
}
case PASSWORD_RESET_SUCCESS:
case PASSWORD_RESET_FAIL:
case ACTIVATION_SUCCESS:
case ACTIVATION_FAIL:
case PASSWORD_RESET_CONFIRM_SUCCESS:
case PASSWORD_RESET_CONFIRM_FAIL:
return {
...state
}
default:
return state
}
};
isAuthenticated
未作为道具传递给 PrivateOutlet。
<Route element={<PrivateOutlet />}> // <-- no isAuthenticated prop
<Route path='/about' element={<About />} />
</Route>
isAuthenticated
存储在redux状态,初始值为null
而不是 true
|false
在成功或失败的身份验证尝试之后。
const initialState = {
access: localStorage.getItem('access'),
refresh: localStorage.getItem('refresh'),
isAuthenticated: null,
user: null,
};
您可以明确检查 isAuthenticated
状态是否为 null
并且有条件地 return 为 null 或加载指示器等...同时正在解析身份验证状态。一旦身份验证状态解析为 non-null 值,则可以呈现路由组件或重定向。
import React from 'react';
import { connect } from 'react-redux';
import { Outlet, Navigate } from 'react-router-dom';
const PrivateOutlet = ({ isAuthenticated }) => {
if (isAuthenticated === null) {
return null;
}
return isAuthenticated ? <Outlet /> : <Navigate to='/login' replace />;
};
const mapStateToProps = state => ({
isAuthenticated: state => state.auth.isAuthenticated,
});
export default connect(mapStateToProps)(PrivateOutlet);
或
import React from 'react';
import { useSelector } from 'react-redux';
import { Outlet, Navigate } from 'react-router-dom';
const PrivateOutlet = () => {
const isAuthenticated = useSelector(state => state.auth.isAuthenticated);
if (isAuthenticated === null) {
return null;
}
return isAuthenticated ? <Outlet /> : <Navigate to='/login' replace />;
};
export default PrivateOutlet;
更新
如果您想将用户重定向回他们最初尝试访问的页面,PrivateOutlet
组件应该获取当前位置并将其在路由状态中传递到登录页面。
import { Outlet, Navigate, useLocation } from 'react-router-dom';
const PrivateOutlet = () => {
const location = useLocation();
const isAuthenticated = useSelector(state => state.auth.isAuthenticated);
if (isAuthenticated === null) {
return null;
}
return isAuthenticated
? <Outlet />
: <Navigate to='/login' state={{ from: location }} replace />;
};
然后 Login
组件从路由状态中获取这个值以强制导航回原始路由。
const navigate = useNavigate();
const { state } = useLocation();
const { from } = state || {};
...
navigate(from.pathname || "/home", { state: from.state, replace: true });
例子
import React, { useState } from 'react';
import { Link, Navigate, useLocation } from 'react-router-dom';
...
function Login({ login, isAuthenticated }) {
const { state } = useLocation();
const { from } = state || {};
const [formData, setFormData] = useState({
email: '',
password: ''
});
const { email, password } = formData;
const onChange = e => setFormData({
...formData,
[e.target.name]: e.target.value
});
const onSubmit = e => {
e.preventDefault();
login(email, password);
};
if (isAuthenticated) {
return (
<Navigate
to={from.pathname || "/home"}
replace
state={from.state}
/>
);
}
return (
...
);
};
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated
});
更新 2
I get on the console; Login.js:30 Uncaught TypeError: Cannot read properties of undefined (reading 'pathname')
我认为这里发生的是 login
操作更新了你的 redux store,应该 触发组件重新渲染,我怀疑它是 this 重新呈现会丢失路由状态。路由状态非常短暂,仅在接收到它时存在于转换和渲染周期中。您可能会使用 React ref 来缓存路由状态的副本以备后用。
示例:
import React, { useRef, useState } from 'react';
import { Link, Navigate, useLocation } from 'react-router-dom';
...
function Login({ login, isAuthenticated }) {
const { state } = useLocation();
const { from } = state || {};
const fromRef = useRef(from);
const [formData, setFormData] = useState({
email: '',
password: ''
});
const { email, password } = formData;
const onChange = e => setFormData({
...formData,
[e.target.name]: e.target.value
});
const onSubmit = e => {
e.preventDefault();
login(email, password);
};
if (isAuthenticated) {
return (
<Navigate
to={fromRef.current.pathname || "/home"}
replace
state={fromRef.current.state}
/>
);
}
return (
...
);
};
将“onLoginSuccess”处理程序传递给 login
操作并从异步操作发出命令式导航可能更实用。
示例:
import React, { useState } from 'react';
import { Link, useNavigate, useLocation } from 'react-router-dom';
...
function Login({ login, isAuthenticated }) {
const navigate = useNavigate();
const { state } = useLocation();
const { from } = state || {};
const [formData, setFormData] = useState({
email: '',
password: ''
});
const { email, password } = formData;
const onChange = e => setFormData({
...formData,
[e.target.name]: e.target.value
});
const onSubmit = e => {
e.preventDefault();
const onSuccess = () => {
navigate(
from.pathname || "/home",
{
replace: true,
state: from.state
}
);
};
login(email, password, onSuccess);
};
return (
...
);
};
...
export const login = (email, password, onSuccess) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ email, password });
try {
const res = await axios.post(
`${process.env.REACT_APP_API_URL}/auth/jwt/create/`,
body,
config
);
dispatch({ type: LOGIN_SUCCESS, payload: res.data });
dispatch(setAlert('Authenticated successfully', 'success'));
dispatch(load_user());
if (onSuccess) {
onSuccess();
}
} catch (err) {
dispatch({ type: LOGIN_FAIL });
dispatch(setAlert('Error Authenticating', 'error'));
}
};
当我尝试下面的代码时,我被重定向到登录页面,就好像我没有通过身份验证一样。登录后我无法查看关于页面,因为它会将我定向到欢迎页面,因为登录页面中的逻辑(如果 isAuthenticated
导航到欢迎页面)。如果我删除登录页面中的逻辑,我只会卡在登录页面中。为什么我无法查看关于页面?
PrivateOutlet.js;
import React from 'react';
import { Outlet, Navigate } from 'react-router-dom';
const PrivateOutlet = ({ isAuthenticated }) => {
if (isAuthenticated ) {
return <Outlet />
} else {
return <Navigate to='login' /> //Go to login
}
};
export default PrivateOutlet;
已更新 PrivateOutlet.js ;
import React from 'react';
import { connect } from 'react-redux';
import { Outlet, Navigate } from 'react-router-dom';
const PrivateOutlet = ({ isAuthenticated }) => {
return isAuthenticated ? <Outlet /> : <Navigate to='/login' replace />;
};
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated
});
export default connect(mapStateToProps)(PrivateOutlet);
App.js
function App() {
return (
<Provider store={store}>
<Router>
<Layout>
<Routes>
<Route path='/' element={<WelcomePage/>} />
<Route path='/home' element={<Home/>} />
<Route element={<PrivateOutlet/>}>
<Route path='/about' element={<About/>} />
</Route>
<Route path='/contact' element={<Contact/>} />
<Route path='/login' element={<Login/>} />
<Route path='/signup' element={<Signup/>} />
<Route path='/reset-password' element={<ResetPassword/>} />
<Route path='/password/reset/confirm/:uid/:token' element={<ResetPasswordConfirm/>} />
<Route path='/activate/:uid/:token' element={<Activate/>} />
<Route path='*' element={<NotFound/>} />
</Routes>
</Layout>
</Router>
</Provider>
);
}
export default App;
login.js
import React, { useState } from 'react';
import { Link, Navigate } from 'react-router-dom';
import { connect } from 'react-redux';
import { Button } from '@mui/material';
import { login } from '../actions/auth';
import './Login.css';
import { Helmet } from 'react-helmet';
function Login({ login, isAuthenticated }) {
const [formData, setFormData] = useState({
email: '',
password: ''
});
const { email, password } = formData;
const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value});
const onSubmit = e => {
e.preventDefault();
login (email, password)
};
if (isAuthenticated) {
return <Navigate to='/' />
}
return (
<div className='login'>
<Helmet>
<title>Prosperity - Login</title>
<meta
name='description'
content='login page'
/>
</Helmet>
<h1 className='login__title'>Login</h1>
<p className='login__lead'>Login into your Account</p>
<form className='login__form' onSubmit={e => onSubmit(e)}>
<div className='login__form__group'>
<input
className='login__form__input'
type='email'
placeholder='Email *'
name='email'
value={email}
onChange={e => onChange(e)}
required
/>
</div>
<div className='login__form__group'>
<input
className='login__form__input'
type='password'
placeholder='Password *'
name='password'
value={password}
onChange={e => onChange(e)}
minLength='8'
required
/>
</div>
<Button className='login__button__main' type='submit'>Login</Button>
</form>
<p className='link__to__Signup'>
Do not have an account? <Link to='/signup' className='login__link'>Register</Link>
</p>
<p className='link__to__resetPassword'>
Forgot Password? <Link to='/reset-password' className='reset__password__link'>Reset Password</Link>
</p>
</div>
)
};
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated
});
export default connect (mapStateToProps, { login }) (Login);
actions/Auth.js ;
import axios from 'axios';
import { setAlert } from './alert';
import {
LOGIN_SUCCESS,
LOGIN_FAIL,
SIGNUP_SUCCESS,
SIGNUP_FAIL,
ACTIVATION_SUCCESS,
ACTIVATION_FAIL,
USER_LOADED_SUCCESS,
USER_LOADED_FAIL,
AUTHENTICATED_SUCCESS,
AUTHENTICATED_FAIL,
PASSWORD_RESET_SUCCESS,
PASSWORD_RESET_FAIL,
PASSWORD_RESET_CONFIRM_SUCCESS,
PASSWORD_RESET_CONFIRM_FAIL,
LOGOUT
} from './types';
export const checkAuthenticated = () => async dispatch => {
if (localStorage.getItem('access')) {
const config = {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
};
const body = JSON.stringify({ token: localStorage.getItem('access') });
try {
const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/jwt/verify/`, body, config)
if (res.data.code !== 'token_not_valid') {
dispatch({
type: AUTHENTICATED_SUCCESS
});
} else {
dispatch({
type: AUTHENTICATED_FAIL
});
}
} catch (err) {
dispatch({
type: AUTHENTICATED_FAIL
});
}
} else {
dispatch({
type: AUTHENTICATED_FAIL
});
}
};
export const load_user = () => async dispatch => {
if (localStorage.getItem('access')) {
const config = {
headers: {
'Content-Type': 'application/json',
'Authorization': `JWT ${localStorage.getItem('access')}`,
'Accept': 'application/json'
}
};
try {
const res = await axios.get(`${process.env.REACT_APP_API_URL}/auth/users/me/`, config);
dispatch({
type: USER_LOADED_SUCCESS,
payload: res.data
});
}catch (err) {
dispatch({
type: USER_LOADED_FAIL
});
}
} else {
dispatch({
type: USER_LOADED_FAIL
});
}
};
export const login = (email, password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ email, password });
try {
const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/jwt/create/`, body, config);
dispatch({
type: LOGIN_SUCCESS,
payload: res.data
});
dispatch(setAlert('Authenticated successfully', 'success'));
dispatch(load_user());
}catch (err) {
dispatch({
type: LOGIN_FAIL
});
dispatch(setAlert('Error Authenticating', 'error'));
}
};
export const signup = (name, email, password, re_password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ name, email, password, re_password });
try {
const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/users/`, body, config);
dispatch({
type: SIGNUP_SUCCESS,
payload: res.data
});
dispatch(setAlert('Check Your Email to Activate Your Account.', 'warning'));
} catch (err) {
dispatch({
type: SIGNUP_FAIL
})
}
};
export const verify = (uid, token) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ uid, token });
try {
await axios.post(`${process.env.REACT_APP_API_URL}/auth/users/activation/`, body, config);
dispatch({
type: ACTIVATION_SUCCESS,
});
dispatch(setAlert('Account Activated Successfully.', 'success'));
} catch (err) {
dispatch({
type: ACTIVATION_FAIL
})
}
};
//Reset Password
export const reset_password = (email) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ email });
try {
await axios.post (`${process.env.REACT_APP_API_URL}/auth/users/reset_password/`, body, config);
dispatch({
type: PASSWORD_RESET_SUCCESS
});
dispatch(setAlert('Check Your Email to Rest Password.', 'warning'));
} catch (err) {
dispatch({
type: PASSWORD_RESET_FAIL
});
}
};
// Reset Password Confirm
export const reset_password_confirm = (uid, token, new_password, re_new_password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ uid, token, new_password, re_new_password });
try {
await axios.post (`${process.env.REACT_APP_API_URL}/auth/users/reset_password_confirm/`, body, config);
dispatch(setAlert('Password Rest Successful.', 'success'));
dispatch({
type: PASSWORD_RESET_CONFIRM_SUCCESS
});
} catch (err) {
dispatch({
type: PASSWORD_RESET_CONFIRM_FAIL
});
}
};
//Logout
export const logout = () => dispatch => {
dispatch(setAlert('Logout successful.', 'success'));
dispatch({
type: LOGOUT
});
};
reducers/Auth.js ;
import {
LOGIN_SUCCESS,
LOGIN_FAIL,
SIGNUP_SUCCESS,
SIGNUP_FAIL,
ACTIVATION_SUCCESS,
ACTIVATION_FAIL,
USER_LOADED_SUCCESS,
USER_LOADED_FAIL,
AUTHENTICATED_SUCCESS,
AUTHENTICATED_FAIL,
PASSWORD_RESET_SUCCESS,
PASSWORD_RESET_FAIL,
PASSWORD_RESET_CONFIRM_SUCCESS,
PASSWORD_RESET_CONFIRM_FAIL,
LOGOUT
} from '../actions/types';
const initialState = {
access: localStorage.getItem('access'),
refresh: localStorage.getItem('refresh'),
isAuthenticated: null,
user: null,
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch(type) {
case AUTHENTICATED_SUCCESS:
return {
...state,
isAuthenticated: true
}
case LOGIN_SUCCESS:
localStorage.setItem('access', payload.access);
localStorage.setItem('refresh', payload.refresh);
return {
...state,
isAuthenticated: true,
access: payload.access,
refresh: payload.refresh,
}
case USER_LOADED_SUCCESS:
return {
...state,
user: payload
}
case SIGNUP_SUCCESS:
return {
...state,
isAuthenticated: false,
}
case AUTHENTICATED_FAIL:
return {
...state,
isAuthenticated: false
}
case USER_LOADED_FAIL:
return {
...state,
user: null
}
case LOGIN_FAIL:
case SIGNUP_FAIL:
case LOGOUT:
localStorage.removeItem('access');
localStorage.removeItem('refresh');
return {
...state,
access: null,
refresh: null,
isAuthenticated: false,
user: null,
}
case PASSWORD_RESET_SUCCESS:
case PASSWORD_RESET_FAIL:
case ACTIVATION_SUCCESS:
case ACTIVATION_FAIL:
case PASSWORD_RESET_CONFIRM_SUCCESS:
case PASSWORD_RESET_CONFIRM_FAIL:
return {
...state
}
default:
return state
}
};
isAuthenticated
未作为道具传递给 PrivateOutlet。
<Route element={<PrivateOutlet />}> // <-- no isAuthenticated prop
<Route path='/about' element={<About />} />
</Route>
isAuthenticated
存储在redux状态,初始值为null
而不是 true
|false
在成功或失败的身份验证尝试之后。
const initialState = {
access: localStorage.getItem('access'),
refresh: localStorage.getItem('refresh'),
isAuthenticated: null,
user: null,
};
您可以明确检查 isAuthenticated
状态是否为 null
并且有条件地 return 为 null 或加载指示器等...同时正在解析身份验证状态。一旦身份验证状态解析为 non-null 值,则可以呈现路由组件或重定向。
import React from 'react';
import { connect } from 'react-redux';
import { Outlet, Navigate } from 'react-router-dom';
const PrivateOutlet = ({ isAuthenticated }) => {
if (isAuthenticated === null) {
return null;
}
return isAuthenticated ? <Outlet /> : <Navigate to='/login' replace />;
};
const mapStateToProps = state => ({
isAuthenticated: state => state.auth.isAuthenticated,
});
export default connect(mapStateToProps)(PrivateOutlet);
或
import React from 'react';
import { useSelector } from 'react-redux';
import { Outlet, Navigate } from 'react-router-dom';
const PrivateOutlet = () => {
const isAuthenticated = useSelector(state => state.auth.isAuthenticated);
if (isAuthenticated === null) {
return null;
}
return isAuthenticated ? <Outlet /> : <Navigate to='/login' replace />;
};
export default PrivateOutlet;
更新
如果您想将用户重定向回他们最初尝试访问的页面,PrivateOutlet
组件应该获取当前位置并将其在路由状态中传递到登录页面。
import { Outlet, Navigate, useLocation } from 'react-router-dom';
const PrivateOutlet = () => {
const location = useLocation();
const isAuthenticated = useSelector(state => state.auth.isAuthenticated);
if (isAuthenticated === null) {
return null;
}
return isAuthenticated
? <Outlet />
: <Navigate to='/login' state={{ from: location }} replace />;
};
然后 Login
组件从路由状态中获取这个值以强制导航回原始路由。
const navigate = useNavigate();
const { state } = useLocation();
const { from } = state || {};
...
navigate(from.pathname || "/home", { state: from.state, replace: true });
例子
import React, { useState } from 'react';
import { Link, Navigate, useLocation } from 'react-router-dom';
...
function Login({ login, isAuthenticated }) {
const { state } = useLocation();
const { from } = state || {};
const [formData, setFormData] = useState({
email: '',
password: ''
});
const { email, password } = formData;
const onChange = e => setFormData({
...formData,
[e.target.name]: e.target.value
});
const onSubmit = e => {
e.preventDefault();
login(email, password);
};
if (isAuthenticated) {
return (
<Navigate
to={from.pathname || "/home"}
replace
state={from.state}
/>
);
}
return (
...
);
};
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated
});
更新 2
I get on the console;
Login.js:30 Uncaught TypeError: Cannot read properties of undefined (reading 'pathname')
我认为这里发生的是 login
操作更新了你的 redux store,应该 触发组件重新渲染,我怀疑它是 this 重新呈现会丢失路由状态。路由状态非常短暂,仅在接收到它时存在于转换和渲染周期中。您可能会使用 React ref 来缓存路由状态的副本以备后用。
示例:
import React, { useRef, useState } from 'react';
import { Link, Navigate, useLocation } from 'react-router-dom';
...
function Login({ login, isAuthenticated }) {
const { state } = useLocation();
const { from } = state || {};
const fromRef = useRef(from);
const [formData, setFormData] = useState({
email: '',
password: ''
});
const { email, password } = formData;
const onChange = e => setFormData({
...formData,
[e.target.name]: e.target.value
});
const onSubmit = e => {
e.preventDefault();
login(email, password);
};
if (isAuthenticated) {
return (
<Navigate
to={fromRef.current.pathname || "/home"}
replace
state={fromRef.current.state}
/>
);
}
return (
...
);
};
将“onLoginSuccess”处理程序传递给 login
操作并从异步操作发出命令式导航可能更实用。
示例:
import React, { useState } from 'react';
import { Link, useNavigate, useLocation } from 'react-router-dom';
...
function Login({ login, isAuthenticated }) {
const navigate = useNavigate();
const { state } = useLocation();
const { from } = state || {};
const [formData, setFormData] = useState({
email: '',
password: ''
});
const { email, password } = formData;
const onChange = e => setFormData({
...formData,
[e.target.name]: e.target.value
});
const onSubmit = e => {
e.preventDefault();
const onSuccess = () => {
navigate(
from.pathname || "/home",
{
replace: true,
state: from.state
}
);
};
login(email, password, onSuccess);
};
return (
...
);
};
...
export const login = (email, password, onSuccess) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ email, password });
try {
const res = await axios.post(
`${process.env.REACT_APP_API_URL}/auth/jwt/create/`,
body,
config
);
dispatch({ type: LOGIN_SUCCESS, payload: res.data });
dispatch(setAlert('Authenticated successfully', 'success'));
dispatch(load_user());
if (onSuccess) {
onSuccess();
}
} catch (err) {
dispatch({ type: LOGIN_FAIL });
dispatch(setAlert('Error Authenticating', 'error'));
}
};