从服务器获取数据时反应路由器 dom 闪烁到辐射路由器 - 反应 6

react router dom flickers to fallout router while fetching data from server - react 6

我正在制作一个 React 应用程序,并且我正在尝试使用我从服务器接收到的数据来创建受保护的路由(无论用户是否经过身份验证,或者他是否是管理员)。 问题是,当我等待从服务器获取数据时,我的 404 fallout 路由出现闪烁。

我的目标:呈现受保护的路由而不闪烁到fallout 404 \ page not found route

问题:由于客户端从服务器发送数据和从服务器接收回数据所花费的时间,导致客户端不知道用户的时间很短已通过身份验证并将他发送到 fallout 页面,只有在完成数据获取后,它才会将他重定向回实际页面。

我的app.tsx-

import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { bindActionCreators } from 'redux';
import { useDispatch, useSelector } from 'react-redux';
import axios from 'axios';

import { ReducersState } from './state/reducers';
import * as actionCreators from './state/reducers/actionCreator';

import { User } from './utils/types';

import Home from './components/pages/Home/Home';
import Login from './components/pages/Login/Login';
import Register from './components/pages/Register/Register';
import Footer from './components/ui/Footer/Footer';
import NavBar from './components/ui/NavBar/NavBar';
import PageNotFound from './components/pages/404/PageNotFound';
import Contact from './components/pages/Contact/Contact';
import ProductsPage from './components/pages/ProductsPage/ProductsPage';
import ProductPage from './components/pages/ProductPage/ProductPage';
import Profile from './components/pages/Profile/Profile';
import AdminPanel from './components/pages/Admin/AdminPanel';
import Tickets from './components/pages/Tickets/Tickets';
import Cart from './components/pages/Cart/Cart';

import './App.scss';

const App = () => {
    const dispacth = useDispatch();

    const auth: { user: User; isAuth: boolean } = useSelector((state: ReducersState) => state.auth);
    const { login } = bindActionCreators(actionCreators, dispacth);

    useEffect(() => {
        if (localStorage.getItem('accessToken')) {
            axios
                .get(process.env.REACT_APP_BACKEND_URL + '/auth/autologin')
                .then((res) => {
                    login(res.data.user);
                })
                .catch((err) => {
                    console.log(err.response.data.message);
                });
        }
    }, []);

    axios.interceptors.request.use(
        (config) => {
            if (config.headers) {
                config.headers.Authorization = `Bearer ${localStorage.getItem('accessToken')}`;
                config.headers.AuthorizationRefresh = `Bearer ${localStorage.getItem('refreshToken')}`;
                config.headers.User = `userId ${localStorage.getItem('refreshToken')}`;

                return config;
            }
        },
        (error) => {
            return Promise.reject(error);
        },
    );

    return (
        <BrowserRouter>
            <NavBar />
            <Routes>
                {/* Routes for all users */}
                <Route path="/" element={<Home />} />
                <Route path="/products-page" element={<ProductsPage />} />
                <Route path="/product-page" element={<ProductPage />} />

                {/* Routes for unauthenticated users */}
                {!auth.isAuth && (
                    <>
                        <Route path="/login" element={<Login />} />
                        <Route path="/register" element={<Register />} />
                    </>
                )}

                {/* Routes for Authenticated users */}
                {auth.isAuth && (
                    <>
                        <Route path="/contact" element={<Contact />} />
                        <Route path="/profile" element={<Profile />} />
                        <Route path="/cart" element={<Cart />} />
                    </>
                )}

                {/* Routes for Admins */}
                {auth.user?.role === 'admin' && (
                    <>
                        <Route path="/admin-panel" element={<AdminPanel />} />
                        <Route path="/ticket-page" element={<Tickets />} />
                    </>
                )}

                <Route path="/*" element={<PageNotFound />} />
            </Routes>
            <Footer />
        </BrowserRouter>
    );
};

App.displayName = 'App';

export default App;

我在 useEffect 调用中获取我的用户数据。

<PageNotFound/> 组件应该有条件地呈现,即等待承诺完成然后 if 来自服务器的响应是 404 或者promise 被拒绝 然后只渲染 <PageNotFound/> 组件。

另一件事:您应该制作一个 <Loading/> 组件,该组件一直呈现到 api 请求正在进行并且没有返回任何响应。

问题

主要问题是代码没有任何主动身份验证检查的意义。它甚至没有未经身份验证的“状态”来促使用户登录 if/when 他们试图访问受保护的路由。

解决方案

要解决身份验证检查期间的逻辑问题,您可以使用加载状态。当组件安装时它应该最初为真,以防止在确认身份验证之前意外的路由访问,并且它应该在任何时候进行身份验证检查时切换为真。

示例:

const { login } = actionCreators;

const App = () => {
  const dispatch = useDispatch();

  const auth: { user: User; isAuth: boolean } = useSelector((state: ReducersState) => state.auth);
  const [isLoading, setIsLoading] = React.useState(true);

  useEffect(() => {
    if (localStorage.getItem('accessToken')) {
      setIsLoading(true);
      axios
        .get(process.env.REACT_APP_BACKEND_URL + '/auth/autologin')
        .then((res) => {
          dispatch(login(res.data.user));
        })
        .catch((err) => {
          console.log(err.response.data.message);
        })
        .finally(() => setIsLoading(false));
    }
  }, []);

  useEffect(() => {
    axios.interceptors.request.use(
      (config) => {
        if (config.headers) {
          config.headers.Authorization = `Bearer ${localStorage.getItem('accessToken')}`;
          config.headers.AuthorizationRefresh = `Bearer ${localStorage.getItem('refreshToken')}`;
          config.headers.User = `userId ${localStorage.getItem('refreshToken')}`;

          return config;
        }
      },
      (error) => {
        return Promise.reject(error);
      },
    );
  }, []);

  if (isLoading) {
    return null; // or loading indicator, spinner, etc...
  }

  return (
    <BrowserRouter>
      <NavBar />
      <Routes>
        ...
      </Routes>
      <Footer />
    </BrowserRouter>
  );
};

建议

将 auth 检查抽象为身份验证包装器组件,直接包装要使用检查的路由。完全不要有条件地渲染路由,只是 allow/prevent 访问的保护逻辑。这消除了设置任何身份验证状态和重定向回用户尝试访问的受保护路由之间的竞争条件。

请参阅 以了解自定义路由包装器处理 authenticated/unauthenticated“状态”并允许路由访问或重定向到特定路由的一般想法,例如要签名的 "/login" 路由用户在.