Home  >  Q&A  >  body text

Title rewritten to: Protected routes in React Route v6 not updating when user logs in

<p>I'm trying to log in a user. I have a login component which receives the credentials and then I use the redux toolkit to store the state and the validation and everything is done in userSlice. I have a protected route that should check if the user is logged in and if the user is not logged in it should not navigate to the recipe page I have. When I try to use the useSelecter hook to access the user from a protected routing component, it returns null on the first render, but does return the user on the second render, but the login still fails. In redux dev tools, the status is updated nicely. Is there a way to get the user object on the first render of the protected routing component? (As you can see I'm using useEffect hook and have dependencies array). </p> <p>Thank you very much for your help. Thanks. </p> <p>Here is my code: </p> <p>Login.js -- This file is responsible for receiving the credentials, then dispatching an action using useDispatch and updating the state using useDispatch. </p> <pre class="brush:php;toolbar:false;">import React, { useState } from 'react'; import { loginUser } from '../../features/users/userSlice'; import { useSelector, useDispatch } from 'react-redux' import { useNavigate } from 'react-router-dom'; export default function Login() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const user = useSelector(state => state.user.user) const dispatch = useDispatch() const navigate = useNavigate() return ( <div> <input type="text" value={email} onChange={(e) => setEmail(e.target.value)} /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} /> <button type="submit" onClick={() => { dispatch(loginUser({email, password})) navigate('/recipes') }}>submit</button> </div> ) }</pre> <p>ProtectedRoute.js -- This component ensures that if the user is not authenticated, he will not be able to log in</p> <pre class="brush:php;toolbar:false;">import React, { useState, useEffect } from "react"; import { Route, Navigate, Outlet } from "react-router-dom"; import { useSelector } from 'react-redux'; export default function ProtectedRoute({ children }) { const [ activeUser, setActiveUser ] = useState(false) const user = useSelector((state) => state.user); useEffect(() => { if (!user.isLoading) { user.success ? setActiveUser(true) : setActiveUser(false) console.log('active user: ' activeUser) } }, [user]) return ( activeUser ? <Outlet /> : <Navigate to="/login"/> ) }</pre> <p>app.js -- This component contains all routes, including protected routes. </p> <pre class="brush:php;toolbar:false;">import React from "react"; import Recipes from "./components/recipes/recipes"; import Login from "./components/users/Login"; import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; import ProtectedRoute from "./utils.js/ProtectedRoute"; const App = () => { return ( <div> <BrowserRouter> <Routes> <Route path="/login" index element={<Login />} /> <Route path="/" element={<Navigate replace to="/login" />}/> <Route element={<ProtectedRoute />}> <Route element={<Recipes/>} path="/recipes" /> </Route> </Routes> </BrowserRouter> </div> ); }; export default App;</pre> <p>userSlice.js -- Since I use the redux toolkit, I have slices with different functions. There are reducers here, which have user status. </p> <pre class="brush:php;toolbar:false;">import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; import axios from "axios"; const loginUrl = 'http://localhost:5000/api/login'; const signupUrl = 'http://localhost:5000/api/signup'; export const loginUser = createAsyncThunk('user/loginUser', async (data) => { const response = await axios.post(loginUrl, data); return response; }) export const signupUser = createAsyncThunk('user/signupUser', async (data) => { const response = await axios.post(signupUrl, data); return response; }) const initialState = { user: {}, isLoading: true } const userSlice = createSlice({ name: 'user', initialState, reducers: { getPassword: (state, action) => { const password = action.payload console.log(password) } }, extraReducers: { [loginUser.pending]: (state) => { state.isLoading = false }, [loginUser.fulfilled]: (state, action) => { state.isLoading = false state.user = action.payload.data }, [loginUser.rejected]: (state) => { state.isLoading = false }, [signupUser.pending]: (state) => { state.isLoading = false }, [signupUser.fulfilled]: (state, action) => { state.isLoading = false state.user = action.payload.data }, [signupUser.rejected]: (state) => { state.isLoading = false }, } }) export const { getPassword } = userSlice.actions export default userSlice.reducer;</pre></p>
P粉297434909P粉297434909438 days ago447

reply all(1)I'll reply

  • P粉043432210

    P粉0434322102023-08-30 12:24:05

    The problem is that the login handler issues two actions at the same time.

    onClick={() => {
      dispatch(loginUser({ email, password })); // <-- 登录用户
      navigate('/recipes');                     // <-- 立即导航
    }}
    

    Before the user authenticates, navigate to the protected route.

    To resolve this issue, the login handler should wait for successful authentication and then redirect to the required route.

    const loginHandler = async () => {
      try {
        const response = await dispatch(loginUser({ email, password })).unwrap();
        if (/* 检查响应条件 */) {
          navigate("/recipes", { replace: true });
        }
      } catch (error) {
        // 处理任何错误或被拒绝的 Promise
      }
    };
    
    ...
    
    
    onClick={loginHandler}
    

    See Handling Thunk Results for more details.

    You can also simplify the ProtectedRoute logic, no additional re-rendering is required, just get the correct output to render. All route guard states can be derived from selected redux states.

    export default function ProtectedRoute() {
      const user = useSelector((state) => state.user);
    
      if (user.isLoading) {
        return null; // 或者加载指示器/旋转器等
       }
    
      return user.success ? <Outlet /> : <Navigate to="/login" replace />;
    }

    reply
    0
  • Cancelreply