我在做受保护的路由.我使用REDUX和纯react 应用程序和react 路由DOM.问题是,当我转到/profile/edit并点击刷新时,它会将我重定向到/login&>/profile.我创建了一条受保护的路由.我想做一些东西,当我在isAuthenticated页的时候,它会留在那一页上.我不想让它把我转到/profile.我把我的protectedRoute投进了element,结果出了问题.现在我在loader号楼里面用.但现在我的protected route坏了.有人能告诉我怎么才能做到这一点吗?或者给我任何 idea ?谢谢

// App.js

<Routes>
  <Route
    element={
      <ProtectedRoute
        isAuthenticatedRoute={false}
        sendTo={"/profile"}
      />
    }
  >
    <Route path="/login" element={<Login />} />
    <Route path="/register" element={<Register />} />
  </Route>

  {/* Profile Pages */}
  <Route
    element={
      <ProtectedRoute
        isAuthenticatedRoute={true}
        sendTo={"/login"}
      />
    }
  >
    <Route path="/profile" element={<Profile />} />
    <Route path="/profile/category" element={<EditCategory />} />
    <Route path="/profile/edit" element={<EditProfile />} />
  </Route>

  {/* Page Not Found */}
  <Route path="/*" element={<PageNotFound />} />
</Routes>

// App.js with loader

<Routes>
  <Route
    loader={
      <ProtectedRoute
        isAuthenticatedRoute={false}
        sendTo={"/profile"}
      />
    }
  >
    <Route path="/login" element={<Login />} />
    <Route path="/register" element={<Register />} />
  </Route>
</Routes>

// ProtectedRoutes.js

import React, { useEffect } from "react";
import { useSelector } from "react-redux";
import { Navigate, Outlet, useNavigate } from "react-router-dom";
import AppLoader from "../AllLoader/AppLoader/AppLoader";
import { useLoadUserQuery } from "../../redux/feature/apiSlice/apiSlice";

const ProtectedRoute = ({
  isAuthenticatedRoute,
  children,
  adminRoute,
  isAdmin,
  sendTo,
}) => {
  const navigate = useNavigate();
  const { isLoading } = useLoadUserQuery(undefined, undefined);

  const { user } = useSelector((state) => state.auth);

  if (isLoading) {
    return <AppLoader />;
  }

  const isAuthenticated = user;

  if (isAuthenticatedRoute && !isAuthenticated) {
    return <Navigate to="/login" replace={true} />;
  }
  if (!isAuthenticatedRoute && isAuthenticated) {
    return <Navigate to="/profile" replace={true} />;
  }

  return children ? children : <Outlet />;
};

export default ProtectedRoute;
My redux store
import { configureStore } from "@reduxjs/toolkit";
import newsSliceReducer from "./feature/newsSlice/newsSlice";
import authSliceReducer from "./feature/userSlice/authSlice";
import profileSliceReducer from "./feature/profileSlice/profileSlice";
import languageSliceReducer from "./feature/languageSlice/languageSlice";
import otherSliceReducer from "./feature/otherSlice/otherSlice";
import { apiSlice } from "./feature/apiSlice/apiSlice";
import { getUserProfile, refreshToken } from "./feature/userSlice/authApi";

const store = configureStore({
    reducer: {
        [apiSlice.reducerPath]: apiSlice.reducer,
        // For Getting all news
        allNews: newsSliceReducer,

        // For Authorization
        auth: authSliceReducer,

        // For user Profile
        profile: profileSliceReducer,

        // For others Data
        other: otherSliceReducer,

        // For Langauge which will be used later
        languages: languageSliceReducer,
    },

    // devTools: false,
    middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware().concat(apiSlice.middleware),
});

// Here will call the Get profile and refresh topke

const initializeApp = async () => {
    await store.dispatch(
        apiSlice.endpoints.refreshToken.initiate({}, { forceRefetch: true })
    );
    await store.dispatch(
        apiSlice.endpoints.loadUser.initiate({}, { forceRefetch: true })
    );

    // await store.dispatch(refreshToken());
    // await store.dispatch(getUserProfile());
};

initializeApp();

export default store;


//apiSlice

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { SERVER } from "../../../utils/backend";
import { userValue } from "../userSlice/authSlice";

export const apiSlice = createApi({
    reducerPath: "api",
    baseQuery: fetchBaseQuery({ baseUrl: SERVER }),
    endpoints: (builder) => ({
        refreshToken: builder.query({
            query: (data) => ({
                url: "/user/refresh-token",
                method: "GET",
                credentials: "include",
            }),
        }),

        // Load users
        loadUser: builder.query({
            query: (data) => ({
                url: "/user/me",
                method: "GET",
                credentials: "include",
            }),

            async onQueryStarted(arg, { queryFulfilled, dispatch }) {
                try {
                    const result = await queryFulfilled;
                    dispatch(
                        userValue({
                            token: result.data.accessToken,
                            user: result.data.user,
                        })
                    );
                } catch (error) {
                    console.log(error.message);
                }
            },
        }),
    }),
});

export const { useRefreshTokenQuery, useLoadUserQuery } = apiSlice;


My ApiSlice
import { createSlice } from "@reduxjs/toolkit";
import {
    activateUser,
    getUserProfile,
    refreshToken,
    userLogin,
    userLogout,
    userRegister,
} from "./authApi";

const userSlice = createSlice({
    name: "user",
    initialState: {
        authLoading: false,
        getUserProfileLoading: false,
        refreshTokenLoading: false,
        user: null,
        userActivationToken: "",
        authError: null,
        authMessage: null,
        getUserProfileMessage: null,
        getUserProfileError: null,
    },
    reducers: {
        clearAuthMessage: (state) => {
            state.authMessage = null;
        },
        clearAuthError: (state) => {
            state.authError = null;
        },
        clearUserProfileError: (state) => {
            state.getUserProfileError = null;
        },

        userValue: (state, action) => {
            state.user = action.payload.user;
        },
    },
    extraReducers: (builder) => {
        // Register

        builder.addCase(userRegister.pending, (state) => {
            state.authLoading = true;
        });
        builder.addCase(userRegister.fulfilled, (state, action) => {
            state.authLoading = false;
            state.authMessage = action.payload.message;
            state.userActivationToken = action.payload.activationToken;
        });

        builder.addCase(userRegister.rejected, (state, action) => {
            state.authLoading = false;
            state.authError = action.payload.message;
        });

        // Login

        builder.addCase(userLogin.pending, (state) => {
            state.authLoading = true;
        });
        builder.addCase(userLogin.fulfilled, (state, action) => {
            state.authLoading = false;
            state.user = action.payload.user;
            state.authMessage = action.payload.message;
        });

        builder.addCase(userLogin.rejected, (state, action) => {
            state.authLoading = false;
            state.authError = action.payload.message;
        });

        // Logout =================================================================

        builder.addCase(userLogout.pending, (state) => {
            state.authLoading = true;
        });
        builder.addCase(userLogout.fulfilled, (state, action) => {
            state.authLoading = false;
            state.user = null;
            state.authMessage = action.payload.message;
        });

        builder.addCase(userLogout.rejected, (state, action) => {
            state.authLoading = false;
            state.authError = action.payload.message;
        });

        // =========================    Active User        ========================================

        builder.addCase(activateUser.pending, (state) => {
            state.authLoading = true;
        });

        builder.addCase(activateUser.fulfilled, (state, action) => {
            state.authLoading = false;
            state.authMessage = action.payload.message;
        });

        builder.addCase(activateUser.rejected, (state, action) => {
            state.authLoading = false;
            state.authError = action.payload.message;
        });

        // =========================    Refresh token     ========================================

        builder.addCase(refreshToken.pending, (state) => {
            state.refreshTokenLoading = true;
        });

        builder.addCase(refreshToken.fulfilled, (state) => {
            state.refreshTokenLoading = false;
        });

        builder.addCase(refreshToken.rejected, (state) => {
            state.refreshTokenLoading = false;
        });

        //========================================  Get User Profile     ========================================

        builder.addCase(getUserProfile.pending, (state) => {
            state.getUserProfileLoading = true;
        });
        builder.addCase(getUserProfile.fulfilled, (state, action) => {
            state.getUserProfileLoading = false;
            state.user = action.payload.user;
        });

        builder.addCase(getUserProfile.rejected, (state, action) => {
            state.getUserProfileLoading = false;
            state.getUserProfileError = action.payload.message;
        });
    },
});

export const {
    clearAuthMessage,
    clearAuthError,
    clearUserProfileError,
    userValue,
} = userSlice.actions;

export default userSlice.reducer;


My index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";
import "./index.css";
import { Provider } from "react-redux";
import store from "./redux/store";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
    <React.StrictMode>
        <Provider store={store}>
            <BrowserRouter>
                <App />
            </BrowserRouter>
        </Provider>
    </React.StrictMode>
);

reportWebVitals();

我期待当我在任何authenticated页,它会留在那里.它不会在第一个/login&>/profile页重定向我

推荐答案

我怀疑在after初始呈现周期完成并且可以在组件中启动查询之前,useLoadUserQuery查询的isLoading状态不会被设置为真.我怀疑在初始呈现周期中,isLoading为假,并且ProtectedRoute使用所 Select 的state.auth.user值的初始状态值null,该值导致呈现Navigate个组件中的一个以重定向用户.

我的建议是从一个不确定的值开始state.auth.user,例如undefined,并在加载时显式判断此值.

例如:

const userSlice = createSlice({
  name: "user",
  initialState: {
    authLoading: false,
    getUserProfileLoading: false,
    refreshTokenLoading: false,
    user: undefined, // <-- initially undefined
    userActivationToken: "",
    authError: null,
    authMessage: null,
    getUserProfileMessage: null,
    getUserProfileError: null,
  },
  reducers: {
    ...
  },
  extraReducers: (builder) => {
    ...
  },
});
const ProtectedRoute = ({
  isAuthenticatedRoute,
  children,
  adminRoute,
  isAdmin,
  sendTo,
}) => {
  const navigate = useNavigate();
  const { isLoading } = useLoadUserQuery(undefined, undefined);

  const user = useSelector((state) => state.auth.user);

  if (isLoading || user === undefined) { // <-- loading, or undefined user
    return <AppLoader />;
  }

  const isAuthenticated = !!user;

  if (isAuthenticatedRoute && !isAuthenticated) {
    return <Navigate to="/login" replace />;
  }
  if (!isAuthenticatedRoute && isAuthenticated) {
    return <Navigate to="/profile" replace />;
  }

  return children ?? <Outlet />;
};

更传统的路由保护实现将保护某些路由不被未经验证的用户访问的逻辑与保护某些路由不被经过验证的用户访问的逻辑分开.将ProtectedRoute重定向到only未经身份验证的用户,并创建另一个组件来为登录类型的路由执行相反的操作.

例如:

const ProtectedRoute = ({ children }) => {
  const navigate = useNavigate();
  const { isLoading } = useLoadUserQuery(undefined, undefined);

  const user = useSelector((state) => state.auth.user);

  if (isLoading || user === undefined) {
    return <AppLoader />;
  }

  const isAuthenticated = !!user;
  
  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }

  return children ?? <Outlet />;
};
const AnonymousRoute = ({ children }) => {
  const navigate = useNavigate();
  const { isLoading } = useLoadUserQuery(undefined, undefined);

  const user = useSelector((state) => state.auth.user);

  if (isLoading || user === undefined) {
    return <AppLoader />;
  }

  const isAuthenticated = !!user;

  if (isAuthenticated) {
    return <Navigate to="/profile" replace />;
  }

  return children ?? <Outlet />;
};
<Routes>
  <Route element={<AnonymousRoute />}>
    <Route path="/login" element={<Login />} />
    <Route path="/register" element={<Register />} />
  </Route>

  <Route element={<ProtectedRoute/>}>
    <Route path="/profile" element={<Profile />} />
    <Route path="/profile/category" element={<EditCategory />} />
    <Route path="/profile/edit" element={<EditProfile />} />
  </Route>

  <Route path="/*" element={<PageNotFound />} />
</Routes>

Javascript相关问答推荐

如何使图像逐渐/平稳地响应(先减少宽度,然后减少高度)

ChartJS:分组堆叠条形图渲染错误

有没有方法在Angular中的bowser选项卡之间移动HTML?

Angular 拦截器错误处理删除方法问题

reaction如何在不使用符号的情况下允许多行返回?

fetch在本地设置相同来源的cookie,但部署时相同的代码不会设置cookie

使用JavaScript单击上一个或下一个特定按钮创建卡滑动器以滑动单个卡

使用TMS Web Core中的HTML模板中的参数调用过程

django无法解析余数:[0] from carray[0]'

yarn安装一个本地npm包,以便本地包使用main项目的node_modules(ckeditor-duplicated-modules错误)

在JavaScript中声明自定义内置元素不起作用

判断表格单元格中是否存在文本框

Angular material 拖放堆叠的牌副,悬停时自动展开&

当运行d3示例代码时,没有显示任何内容

使用Java脚本根据按下的按钮更改S文本

PDF工具包阿拉伯字体的反转数字

更改预请求脚本中重用的JSON主体变量- Postman

Web Crypto API解密失败,RSA-OAEP

Webpack在导入前混淆文件名

FileReader()不能处理Firefox和GiB文件