typescript 无法在react-router外部使用“useLocation”< RouterProvider>未捕获错误:useLocation()只能在< Router>

mnemlml8  于 2023-02-25  发布在  TypeScript
关注(0)|答案(1)|浏览(326)

我尝试在组件中使用useLocation钩子:

import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { connect } from 'react-redux';
import { useNProgress } from '@tanem/react-nprogress';
import type { PayloadAction } from '@reduxjs/toolkit';

import type { AppState } from '@/store/app';
import { uiActions } from '@/store/reducers/ui';

import EDProgressBarView from './EDProgressBar.view';

interface IPropsFromState {
    readonly isProgressBarVisible: boolean;
}

interface IPropsFromDispatch {
    readonly closeProgressBar: () => PayloadAction;
}

interface IProps extends IPropsFromState, IPropsFromDispatch {}

const EDProgressBar: React.FC<IProps> = (props: React.PropsWithChildren<IProps>) => {
    const { animationDuration, isFinished, progress } = useNProgress({
        isAnimating: props.isProgressBarVisible,
        incrementDuration: 200,
    });

    const location = useLocation();

    useEffect(() => {
        props.closeProgressBar();
    }, [location]);

    return (
        <EDProgressBarView
            animationDuration={animationDuration}
            isFinished={isFinished}
            progress={progress}
        />
    );
};

EDProgressBar.displayName = 'EDProgressBar';
EDProgressBar.defaultProps = {};

const mapStateToProps = (state: AppState) => {
    return {
        isProgressBarVisible: state.ui.isProgressBarVisible,
    };
};

export default connect(mapStateToProps, {
    closeProgressBar: uiActions.closeProgressBar,
})(React.memo(EDProgressBar));

我把这个组件放在这里:

import React, { Suspense, useMemo } from 'react';
import { RouterProvider, createBrowserRouter } from 'react-router-dom';

import EDNotification from '@/ui/EDNotification';
import EDProgressBar from '@/ui/EDProgressBar';

import RouterBuilder from './App.router';

interface IProps {
    readonly isAuthenticated: boolean | null;
}

const AppView: React.FC<IProps> = (props: React.PropsWithChildren<IProps>) => {
    const routes = useMemo(() => {
        return RouterBuilder(props.isAuthenticated);
    }, [props.isAuthenticated]);

    return (
        <Suspense fallback={null}>
            <RouterProvider router={createBrowserRouter(routes)} fallbackElement={null} />

            <div id="backdrop-root" />
            <div id="overlay-root" />

            <EDNotification />
            <EDProgressBar />
        </Suspense>
    );
};

AppView.displayName = 'AppView';
AppView.defaultProps = {};

export default React.memo(AppView);

然后我得到一个错误:

Uncaught Error: useLocation() may be used only in the context of a <Router> component.

但是我不明白如果RouterProvider组件不接受类似组件的任何子组件来注入它,我怎么能做这样的事情。我需要这个EDProgressBar组件存在于应用程序中,而不管活动路由是什么。我需要它存在于每个页面中。
我怎么能这样做呢?
这是我的RouterBuild:

import React from 'react';
import { type RouteObject } from 'react-router-dom';

const CliAuth = React.lazy(() => import('./pages/CliAuth'));
const CliAuthenticated = React.lazy(() => import('./pages/CliAuthenticated'));
const NotFound = React.lazy(() => import('./pages/NotFound'));

const RouterBuilder = (isAuthenticated: boolean | null) => {

    const generalRoutes: RouteObject[] = [
        {
            path: 'cli-auth',
            element: <CliAuth />,
        },
        {
            path: 'cli-authenticated',
            element: <CliAuthenticated />,
        },
        {
            path: 'not-found',
            element: <NotFound />,
        },
        {
            path: '*',
            element: isAuthenticated === null ? null : <NotFound />,
        },
    ];

    return [...(isAuthenticated ? authorizedRoutes : unAuthorizedRoutes), ...generalRoutes];
};

export default RouterBuilder;

注意,我删除了一些路由,因为它们在这里是无关紧要的,但重要的是,我在一些路由中使用了loader键,所以我需要这个构建器。

moiiocjp

moiiocjp1#

useLocation和其他RRD钩子不能在路由器提供的任何路由上下文之外使用。我建议创建一个布局路由来呈现EDNotificationEDProgressBar组件以及背景和覆盖div。这个布局路由将包含在RouterBuilder返回的路由中。这允许访问路由上下文的组件在ReactTree中拥有比它们更高的路由器。
示例:

import { Outlet } from 'react-router-dom';

const AppLayout = () => (
  <>
    <Outlet />
    <div id="backdrop-root" />
    <div id="overlay-root" />

    <EDNotification />
    <EDProgressBar />
  </>
);

const RouterBuilder = (isAuthenticated: boolean | null) => {
  const generalRoutes: RouteObject[] = [
    {
      path: 'cli-auth',
      element: <CliAuth />,
    },
    {
      path: 'cli-authenticated',
      element: <CliAuthenticated />,
    },
    {
      path: 'not-found',
      element: <NotFound />,
    },
    {
      path: '*',
      element: isAuthenticated === null ? null : <NotFound />,
    },
  ];

  const routes = [
    {
      element: <AppLayout />
      children: [
        ...(isAuthenticated ? authorizedRoutes : unAuthorizedRoutes),
        ...generalRoutes
      ],
    }
  ];

  return routes;
};
const AppView: React.FC<IProps> = (props: React.PropsWithChildren<IProps>) => {
  const routes = useMemo(() => {
    return RouterBuilder(props.isAuthenticated);
  }, [props.isAuthenticated]);

  return (
    <Suspense fallback={null}>
      <RouterProvider
        router={createBrowserRouter(routes)}
        fallbackElement={null}
      />
    </Suspense>
  );
};

相关问题