英文:
How can I stop react's router private route from redirecting to login page on refresh?
问题
以下是代码部分的翻译:
PrivateRoute.js
import { useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import Spinner from '../layout/Spinner';
const PrivateRoute = ({ children }) => {
const { loading, isAuthenticated } = useSelector((state) => state.auth);
if (loading) return <Spinner />
if (!isAuthenticated) return <Navigate to='/login' />
return children;
}
export default PrivateRoute;
App.js
import './App.css';
import 'react-phone-number-input/style.css';
import { useEffect } from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { loadUser } from './reducers/authSlice';
import setAuthToken from './utils/setAuthToken';
import Products from './screens/Products';
import Product from './screens/Product';
import Navigator from './components/layout/Navigator';
import Footer from './components/layout/Footer';
import Signup from './screens/Signup';
import Login from './screens/Login';
import Dashboard from './screens/Dashboard';
import Settings from './screens/Settings';
import PrivateRoute from './components/routing/PrivateRoute';
import Store from './screens/Store';
import CreateOrUpdateStore from './screens/CreateOrUpdateStore';
import AddProduct from './screens/AddProduct';
import UpdateProduct from './screens/UpdateProduct';
const token = localStorage.getItem("token");
if (token) {
setAuthToken(token);
}
function App() {
const dispatch = useDispatch();
const { isAuthenticated } = useSelector((state) => state.auth);
useEffect(() => {
if (!isAuthenticated && token) {
dispatch(loadUser());
}
}, []);
return (
<Router>
<Navigator />
<div className="container">
<Routes>
<Route path="/" element={<Products />} />
<Route path="/products/:id" element={<Product />} />
<Route path="/signup" element={<Signup />} />
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={<PrivateRoute><Dashboard /></PrivateRoute>} />
<Route path="/settings" element={<PrivateRoute><Settings /></PrivateRoute>} />
<Route path="/store" element={<PrivateRoute><Store /></PrivateRoute>} />
<Route path="/create-store" element={<PrivateRoute><CreateOrUpdateStore /></PrivateRoute>} />
<Route path="/update-store" element={<PrivateRoute><CreateOrUpdateStore /></PrivateRoute>} />
<Route path="/add-product" element={<PrivateRoute ><AddProduct /></PrivateRoute>} />
<Route path="/update-product/:id" element={<PrivateRoute><UpdateProduct /></PrivateRoute>} />
</Routes>
</div>
<Footer />
</Router>
)
}
export default App;
Login.js
import { useState, useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import InputField from '../components/layout/InputField';
import Button from '../components/layout/Button';
import { faEnvelope, faUserCircle, faLock, faArrowRightToBracket } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { loginUser, clearMessages } from '../reducers/authSlice';
import FormAlert from '../components/layout/FormAlert';
import Spinner from '../components/layout/Spinner';
const Login = () => {
const [formData, setFormData] = useState({
email: '',
password: ''
});
const { email, password } = formData;
const dispatch = useDispatch();
const { errors, message, loading, isAuthenticated } = useSelector((state) => state.auth);
useEffect(() => {
dispatch(clearMessages());
},[dispatch]);
const onChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const getError = (name) => {
const findError = errors.filter(error => error.param === name);
if (findError.length > 0) {
const error = errors.find(error => error.param === name);
return error;
}
}
const onSubmit = (e) => {
e.preventDefault();
const data = { email, password };
dispatch(loginUser(data));
setTimeout(() => {
dispatch(clearMessages());
}, 3000);
}
if (loading) return <Spinner />;
if (isAuthenticated) return <Navigate to="/dashboard" />
return (
<form onSubmit={(e) => onSubmit(e)}>
<FontAwesomeIcon style={{ backgroundColor: 'inherit', color: '#2596be', marginTop: '20px' }} size='8x' icon={faUserCircle} />
{JSON.stringify(message) !== '{}' ? (<FormAlert alert={message} />) : ''}
<InputField type='text' label='Email' name='email' value={email} changeHandler={onChange} error={getError('email')} icon={faEnvelope} />
<InputField type='password' label='Password' name='password' value={password} changeHandler={onChange} error={getError('password')} icon={faLock} />
<Button text='LOGIN' loading={loading} icon={faArrowRightToBracket} />
</form>
);
}
export default Login;
authSlice.js(这是 Redux 的一个 reducer 示例,内容较长,未全文翻译,如果需要请提供具体内容或问题)。
如果你有其他需要或有疑问,请随时告诉我。
英文:
PrivateRoute.js
import { useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import Spinner from '../layout/Spinner';
const PrivateRoute = ({ children }) => {
const { loading, isAuthenticated } = useSelector((state) => state.auth);
if (loading) return <Spinner />
if (!isAuthenticated) return <Navigate to='/login' />
return children;
}
export default PrivateRoute;
App.js
import './App.css';
import 'react-phone-number-input/style.css'
import { useEffect } from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { loadUser } from './reducers/authSlice';
import setAuthToken from './utils/setAuthToken';
import Products from './screens/Products';
import Product from './screens/Product';
import Navigator from './components/layout/Navigator';
import Footer from './components/layout/Footer';
import Signup from './screens/Signup';
import Login from './screens/Login';
import Dashboard from './screens/Dashboard';
import Settings from './screens/Settings';
import PrivateRoute from './components/routing/PrivateRoute';
import Store from './screens/Store';
import CreateOrUpdateStore from './screens/CreateOrUpdateStore';
import AddProduct from './screens/AddProduct';
import UpdateProduct from './screens/UpdateProduct';
const token = localStorage.getItem("token");
if (token) {
setAuthToken(token);
}
function App() {
const dispatch = useDispatch();
const { isAuthenticated } = useSelector((state) => state.auth);
useEffect(() => {
if (!isAuthenticated && token) {
dispatch(loadUser());
}
}, []);
return (
<Router>
<Navigator />
<div className="container">
<Routes>
<Route path="/" element={<Products />} />
<Route path="/products/:id" element={<Product />} />
<Route path="/signup" element={<Signup />} />
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={<PrivateRoute><Dashboard /></PrivateRoute>} />
<Route path="/settings" element={<PrivateRoute><Settings /></PrivateRoute>} />
<Route path="/store" element={<PrivateRoute><Store /></PrivateRoute>} />
<Route path="/create-store" element={<PrivateRoute><CreateOrUpdateStore /></PrivateRoute>} />
<Route path="/update-store" element={<PrivateRoute><CreateOrUpdateStore /></PrivateRoute>} />
<Route path="/add-product" element={<PrivateRoute ><AddProduct /></PrivateRoute>} />
<Route path="/update-product/:id" element={<PrivateRoute><UpdateProduct /></PrivateRoute>} />
</Routes>
</div>
<Footer />
</Router>
)
}
export default App;
Login.js
import { useState, useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import InputField from '../components/layout/InputField';
import Button from '../components/layout/Button';
import { faEnvelope, faUserCircle, faLock, faArrowRightToBracket } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { loginUser, clearMessages } from '../reducers/authSlice';
import FormAlert from '../components/layout/FormAlert';
import Spinner from '../components/layout/Spinner';
const Login = () => {
const [ formData, setFormData ] = useState({
email:'',
password:''
});
const { email, password } = formData;
const dispatch = useDispatch();
const { errors, message, loading, isAuthenticated } = useSelector((state) => state.auth);
useEffect(() => {
dispatch(clearMessages());
},[dispatch]);
const onChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const getError = (name) => {
const findError = errors.filter(error => error.param === name);
if (findError.length > 0) {
const error = errors.find(error => error.param === name);
return error;
}
}
const onSubmit = (e) => {
e.preventDefault();
const data = { email, password };
dispatch(loginUser(data));
setTimeout(() => {
dispatch(clearMessages());
},3000);
}
if (loading) return <Spinner />
if (isAuthenticated) return <Navigate to="/dashboard" />
return (
<form onSubmit={(e) => onSubmit(e)}>
<FontAwesomeIcon style={{ backgroundColor: 'inherit', color: '#2596be', marginTop: '20px' }} size='8x' icon={faUserCircle} />
{JSON.stringify(message) !== '{}' ? (<FormAlert alert={message} />) : ''}
<InputField type='text' label='Email' name='email' value={email} changeHandler={onChange} error={getError('email')} icon={faEnvelope} />
<InputField type='password' label='Password' name='password' value={password} changeHandler={onChange} error={getError('password')} icon={faLock} />
<Button text='LOGIN' loading={loading} icon={faArrowRightToBracket} />
</form>
);
}
export default Login;
authSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { authAPI } from '../services/authAPI';
// Function for logging in user
export const loginUser = createAsyncThunk(
"auth/loginUserStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.loginUser(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for loading user data
export const loadUser = createAsyncThunk(
"auth/loadUserStatus",
async (_, { rejectWithValue }) => {
try {
const response = await authAPI.loadUser();
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for signing up a user
export const signupUser = createAsyncThunk(
"auth/signupUserStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.signupUser(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for verifying user accounts after sign up
export const verifyUser = createAsyncThunk(
"auth/verifyUserStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.verifyUser(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for resending otp
export const resendOtp = createAsyncThunk(
"auth/resendOtpStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.resendOtp(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for resending otp
export const retrievePassword = createAsyncThunk(
"auth/retrievePasswordStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.retrievePassword(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for generating authentication token when reseting forgotten password
export const generateToken = createAsyncThunk(
"auth/generateTokenStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.generateToken(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for creating new password
export const createNewPassword = createAsyncThunk(
"auth/createNewPasswordStatus",
async (token, data, { rejectWithValue }) => {
try {
const response = await authAPI.createNewPassword(token, data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for updating user profile
export const updateAccount = createAsyncThunk(
"auth/updateAccountStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.editAccount(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for changing the logged in user password
export const changePassword = createAsyncThunk(
"auth/changePasswordStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.changePassword(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for uploading user profile picture
export const photoUpload = createAsyncThunk(
"auth/uploadPhotoStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.uploadPicture(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
)
// The initial state of the reducer
const initialState = {
token: localStorage.getItem("token"),
isAuthenticated: false,
loading: false,
message: {},
errors: [],
user: null,
passwordResetToken: '',
accountUpdating: false,
passwordChangeLoading: false,
photoUploadMessage: {},
photoUploadLoading: false
};
// The auth slice
export const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
clearMessages: (state) => {
state.message = {};
state.errors = [];
state.photoUploadMessage = {};
},
logout: (state) => {
state.token = localStorage.removeItem("token");
state.isAuthenticated = false;
state.user = null;
}
},
extraReducers: (builder) => {
builder.addCase(loginUser.pending, (state, { payload }) => {
state.user = null;
state.message = {};
state.errors = [];
state.loading = true;
});
builder.addCase(loginUser.fulfilled, (state, { payload }) => {
if (payload.status === 'created') {
state.message = payload;
} else {
localStorage.setItem("token", payload.data);
}
state.loading = false;
state.isAuthenticated = true;
});
builder.addCase(loginUser.rejected, (state, { payload }) => {
if (payload.errors.length === 1) {
state.message = payload.errors[0];
} else {
state.errors = payload.errors;
}
state.loading = false;
state.isAuthenticated = false;
});
builder.addCase(loadUser.pending, (state, { payload }) => {
state.errors = [];
state.message = {};
state.loading = true;
state.isAuthenticated = false;
});
builder.addCase(loadUser.fulfilled, (state, { payload }) => {
state.user = payload.data;
state.isAuthenticated = true;
state.loading = false;
});
builder.addCase(loadUser.rejected, (state, { payload }) => {
state.loading = false;
state.message = payload;
state.user = null;
});
builder.addCase(signupUser.pending, (state, { payload }) => {
state.message = {};
state.user = null;
state.errors = [];
state.loading = true;
});
builder.addCase(signupUser.fulfilled, (state, { payload }) => {
state.loading = false;
state.message = payload;
state.user = { phonenumber: payload.data };
});
builder.addCase(signupUser.rejected, (state, { payload }) => {
state.loading = false;
if (payload.errors.length === 1) {
state.message = payload.errors[0];
} else {
state.errors = payload.errors;
}
});
builder.addCase(verifyUser.pending, (state, { payload }) => {
state.message = {};
state.errors = [];
state.loading = true;
});
builder.addCase(verifyUser.fulfilled, (state, { payload }) => {
state.user = null;
state.message = payload;
state.loading = false;
});
builder.addCase(verifyUser.rejected, (state, { payload }) => {
state.message = payload.errors[0];
state.loading = false;
});
builder.addCase(resendOtp.pending, (state, { payload }) => {
state.message = {};
state.loading = true;
state.errors = [];
});
builder.addCase(resendOtp.fulfilled, (state, { payload }) => {
state.loading = false;
state.message = payload;
});
builder.addCase(resendOtp.rejected, (state, { payload }) => {
state.loading = false;
state.message = payload.errors[0];
});
builder.addCase(retrievePassword.pending, (state, { payload }) => {
state.message = {};
state.loading = true;
state.errors = [];
});
builder.addCase(retrievePassword.fulfilled, (state, { payload }) => {
state.loading = false;
state.message = payload;
});
builder.addCase(retrievePassword.rejected, (state, { payload }) => {
state.loading = false;
state.errors = payload.errors;
});
builder.addCase(generateToken.pending, (state, { payload }) => {
state.message = {};
state.passwordResetToken = '';
state.loading = true;
state.errors = [];
});
builder.addCase(generateToken.fulfilled, (state, { payload }) => {
state.loading = false;
state.passwordResetToken = payload.data;
});
builder.addCase(generateToken.rejected, (state, { payload }) => {
state.loading = false;
state.errors = payload.errors;
});
builder.addCase(createNewPassword.pending, (state, { payload }) => {
state.message = {};
state.loading = true;
state.errors = [];
});
builder.addCase(createNewPassword.fulfilled, (state, { payload }) => {
state.loading = false;
state.message = payload;
});
builder.addCase(createNewPassword.rejected, (state, { payload }) => {
state.loading = false;
state.errors.push(payload);
});
builder.addCase(updateAccount.pending, (state, { payload }) => {
state.accountUpdating = true;
});
builder.addCase(updateAccount.fulfilled, (state, { payload }) => {
state.accountUpdating = false;
state.user = payload.data;
const { data, ...rest } = payload;
state.message = rest;
});
builder.addCase(updateAccount.rejected, (state, { payload }) => {
state.accountUpdating = false;
if (payload.errors && payload.errors.length > 0) {
state.errors = payload.errors;
} else {
state.message = payload;
}
});
builder.addCase(changePassword.pending, (state, { payload }) => {
state.passwordChangeLoading = true;
});
builder.addCase(changePassword.fulfilled, (state, { payload }) => {
state.passwordChangeLoading = false;
state.message = payload;
});
builder.addCase(changePassword.rejected, (state, { payload }) => {
state.passwordChangeLoading = false;
if (payload.errors) {
state.errors = payload.errors;
} else {
state.message = payload;
}
});
builder.addCase(photoUpload.pending, (state, { payload }) => {
state.photoUploadLoading = true;
});
builder.addCase(photoUpload.fulfilled, (state, { payload }) => {
state.photoUploadLoading = false;
const { data, ...rest } = payload;
state.photoUploadMessage = rest;
state.user = data;
});
builder.addCase(photoUpload.rejected, (state, { payload }) => {
state.photoUploadLoading = false;
state.photoUploadMessage = payload;
});
}
});
export const { clearMessages, logout } = authSlice.actions;
export default authSlice.reducer;
I tried creating a private route and preventing the loadUser action from running on refresh if the user is already logged in by creating a condition on the App.js; but this method still prove to be ineffective in solving the problem.
答案1
得分: 2
使用一个最初为真的 loading
状态,以便在 PrivateRoute
组件检查 isAuthenticated
状态并重定向之前,App
中的 useEffect
有机会运行并检查身份验证状态。您还可以初始化 isAuthenticated
为 true,如果本地存储中有持久化的令牌。
authSlice.js
const initialState = {
token: localStorage.getItem("token"),
isAuthenticated: !!localStorage.getItem("token"),
loading: true,
message: {},
errors: [],
user: null,
passwordResetToken: '',
accountUpdating: false,
passwordChangeLoading: false,
photoUploadMessage: {},
photoUploadLoading: false
};
const PrivateRoute = ({ children }) => {
const { loading, isAuthenticated } = useSelector((state) => state.auth);
if (loading) return <Spinner />;
if (!isAuthenticated) return <Navigate to='/login' />;
return children;
}
英文:
Use a loading
state that is initially true so that the useEffect
in App
has a chance to run and check the auth status prior to the PrivateRoute
component checking the isAuthenticated
state and redirecting. You may also want to initialize isAuthenticated
to true of there was a persisted token in localStorage.
authSlice.js
const initialState = {
token: localStorage.getItem("token"),
isAuthenticated: !!localStorage.getItem("token"),
loading: true,
message: {},
errors: [],
user: null,
passwordResetToken: '',
accountUpdating: false,
passwordChangeLoading: false,
photoUploadMessage: {},
photoUploadLoading: false
};
const PrivateRoute = ({ children }) => {
const { loading, isAuthenticated } = useSelector((state) => state.auth);
if (loading) return <Spinner />;
if (!isAuthenticated) return <Navigate to='/login' />;
return children;
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论