简体   繁体   English

升级 React-router-dom v6 多个错误

[英]Upgrading React-router-dom v6 multiple errors

So I've been upgrading modules for a project that is not mine so I'm not familiar with the code, when I tried upgrading react-router-dom from 5.2.0 ❯ 6.3.0 I stumbled with the way the routes are implemented.所以我一直在为一个不是我的项目升级模块,所以我不熟悉代码,当我尝试从 5.2.0 ❯ 6.3.0 升级 react-router-dom 时,我偶然发现了路由的实现方式. Having replaced the reprecated modules I get multiple errors that basically say the routes are not being defined correctly but I tried everything and I can't get it to work.替换了reprecated模块后,我收到多个错误,基本上说路由没有正确定义,但我尝试了所有方法,但无法正常工作。 These are the errors I get when I load the page.这些是我加载页面时遇到的错误。

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Matched leaf route at location "/" does not have an element. This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.

Uncaught Error: [Navigate] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>

This is the app component that deals with the routes.这是处理路由的应用程序组件。

import routes from './App.routes'
import { lazy } from 'react'
import AppContainer from './containers/app'
import AuthContainer from './containers/auth'
import { Route, Routes, Navigate, useNavigate, useRoutes } from 'react-router-dom'
import { ModalError } from './components/ModalError/ModalError'
import { EnvironmentProvider } from './contexts/Environment'
import { useDispatch, useSelector } from 'react-redux'
import { hiddenError } from './store/slices/health'
import { FullScreenLoading } from './components/FullScreenLoading/FullScreenLoading'
import { Suspense, useEffect, useState } from 'react'
import * as actionsProfile from './store/slices/profile'
import { firebaseInit, getValuesRemoteConfig } from './service/firebase'
import { setSecret } from './service/cryptojs'
import axios from './config/axios'
import Maintenance from './pages/common/maintenance/maintenance'
import { googleAnalyticsRegisterView } from './components/GoogleAnalytics/GoogleAnalytics'

const App = () => {
  const [isLoadingConfig, setIsLoadingConfig] = useState(true)

  const [configBeSetted, setConfigBeSetted] = useState(false)

  const [appInitialized, setAppInitialized] = useState(false)

  const [configuration, setConfiguration] = useState({
    isMaintenanceWebMessage: null,
    isMaintenanceWeb: null,
    apiUrlRemote: null,
    timeoutRemote: null,
    secretRemote: null,
    publicWebUrl: null,
    publicTurnsUrl: null,
    successRedirectUri: null,
    errorRedirectUri: null,
    googlePlayUrl: null,
    appStoreUrl: null,
  })

  const token = JSON.parse(localStorage.getItem('tsu'))?.token

  const { showError } = useSelector((state) => state.health.health)

  const dispatch = useDispatch()

  useEffect(() => {
    firebaseInit(async (app) => {
      const config = await getValuesRemoteConfig(app)

      //Production
      axios.defaults.baseURL = config.apiUrlRemote
      axios.defaults.timeout = config.timeoutRemote
      setSecret(config.secretRemote)

      setConfiguration(config)

      setIsLoadingConfig(false)

      setConfigBeSetted(true)

      setAppInitialized(true)

      token && dispatch(actionsProfile.getProfileData())
    })
  }, [token])

  if (isLoadingConfig || !configBeSetted) return <FullScreenLoading />

  if (configBeSetted && configuration?.isMaintenanceWeb) {
    return (
      <EnvironmentProvider config={configuration}>
        <Maintenance message={configuration?.isMaintenanceWebMessage} />
      </EnvironmentProvider>
    )
  }
  return (
    <Suspense fallback={<FullScreenLoading />}>
      <EnvironmentProvider config={configuration}>
        {showError && <ModalError onClose={() => dispatch(hiddenError())} />}
        <Routes>
          {routes
            .map((route) => ({
              ...route,
              component: appInitialized
                ? googleAnalyticsRegisterView(route.component)
                : route.component,
            }))
            .map((route, i) => {
              const Container = route.private ? AppContainer : AuthContainer
              return (
                <Route
                  key={i}
                  path={route.path}
                  exact={route.exact}
                  render={(props) => {
                    return (
                      <Container key={i}>
                        <route.component {...props} />
                      </Container>
                    )
                  }}
                />
              )
            })}
          <Navigate to={token ? '/home' : '/'} />
        </Routes>
      </EnvironmentProvider>
    </Suspense>
  )
}

export default App

This is the routes component.这是路由组件。

import { lazy } from 'react'
import { googleAnalyticsRegisterView } from './components/GoogleAnalytics/GoogleAnalytics'

const Home = lazy(() => import('./pages/app/home'))
const MyData = lazy(() => import('./pages/app/my-data'))
const ClientAttention = lazy(() => import('./pages/app/client-atention'))
const PhoneAssistance = lazy(() => import('./pages/app/phone-assitance'))
const InsuranceDetails = lazy(() => import('./pages/app/insurance-details'))
const FeePayment = lazy(() => import('./pages/app/fee-payment'))
const MyInsurance = lazy(() => import('./pages/app/my-insurance'))
const ReportClaim = lazy(() => import('./pages/app/report-claim'))
const Notifications = lazy(() => import('./pages/app/notifications'))
const Login = lazy(() => import('./pages/auth/login'))
const RecoverPassword = lazy(() => import('./pages/auth/recover-password'))
const Register = lazy(() => import('./pages/auth/register'))
const NewPassword = lazy(() => import('./pages/auth/new-password'))
const FrecuentlyQuestions = lazy(() =>
  import('./pages/app/frecuently-question')
)
const ProofOfPayment = lazy(() =>
  import('./pages/common/proof-of-payment/ProofOfPayment')
)
const SuccessPayment = lazy(() =>
  import('./pages/common/payments/success/SuccessPayment')
)
const ErrorPayment = lazy(() =>
  import('./pages/common/payments/error/ErrorPayment')
)
const AppRedirect = lazy(() => import('./pages/auth/app-redirect'))

export const privateRoutes = [
  '/home',
  '/my-data',
  '/phone-assistance',
  '/client-attention',
  '/details',
  '/fee-payment',
  '/my-insurances',
  '/report-claim',
  '/notifications',
  '/faqs',
]

export const publicRoutes = [
  '/',
  '/recover-password',
  '/new-password',
  '/register',
  '/certifacates',
  '/app-redirect',
]

const routes = [
  {
    component: SuccessPayment,
    path: '/success-payment',
    exact: true,
    private: !!localStorage.getItem('tsu'),
  },
  {
    component: ErrorPayment,
    path: '/error-payment',
    exact: true,
    private: !!localStorage.getItem('tsu'),
  },
  {
    component: Login,
    path: '/',
    exact: true,
    private: false,
  },
  {
    component: RecoverPassword,
    path: '/recover-password',
    exact: true,
    private: false,
  },
  {
    component: NewPassword,
    path: '/new-password',
    exact: true,
    private: false,
  },
  {
    component: Register,
    path: '/register',
    exact: true,
    private: false,
  },
  {
    component: Home,
    path: '/home',
    exact: true,
    private: true,
  },
  {
    component: MyData,
    path: '/my-data',
    exact: true,
    private: true,
  },
  {
    component: PhoneAssistance,
    path: '/phone-assistance',
    exact: true,
    private: true,
  },
  {
    component: ClientAttention,
    path: '/client-attention',
    exact: true,
    private: true,
  }
]

export default routes

And this is the main component where app is rendered这是渲染应用程序的主要组件

import React from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
import GlobalStyles from './global-styles'
import ErrorBoundary from './components/ErrorBoundary/ErrorBoundary'
import store, { history } from './store'
import { HashRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import * as Sentry from '@sentry/react'
import { BrowserTracing } from '@sentry/tracing'

Sentry.init({
  dsn: import.meta.env.VITE_SENTRY_KEY,
  integrations: [new BrowserTracing()],
  tracesSampleRate: 1.0,
})

const container = document.getElementById('root')
const root = createRoot(container) // createRoot(container!) if you use TypeScript
root.render(
  <React.StrictMode>
    <ErrorBoundary>
      <Provider store={store}>
        <HashRouter ref={history}>
          <GlobalStyles />
          <App />
        </HashRouter>
      </Provider>
    </ErrorBoundary>
  </React.StrictMode>
)

History is exported like export const history = createRef();历史被导出,如 export const history = createRef();

I know there is something about that ref property or HashRouter but in the documentation I can't find it.我知道那个 ref 属性或 HashRouter 有一些东西,但在文档中我找不到它。

Issue 1第一期

Warning: Function components cannot be given refs.警告:不能给 Function 组件提供参考。 Attempts to access this ref will fail.尝试访问此 ref 将失败。 Did you mean to use React.forwardRef()?你的意思是使用 React.forwardRef() 吗?

This does indeed appear to be related to the HashRouter component.这确实似乎与HashRouter组件有关。 You can look at the type declaration and see that the HashRouter doesn't consume a ref prop.您可以查看类型声明并看到HashRouter不使用ref道具。

 declare function HashRouter( props: HashRouterProps ): React.ReactElement; interface HashRouterProps { basename?: string; children?: React.ReactNode; window?: Window; }

And also inspect the source code to verify it also doesn't forward any React ref.并且还要检查源代码以验证它也没有转发任何 React ref。

 /** * A `<Router>` for use in web browsers. Stores the location in the hash * portion of the URL so it is not sent to the server. */ export function HashRouter({ basename, children, window }: HashRouterProps) { let historyRef = React.useRef<HashHistory>(); if (historyRef.current == null) { historyRef.current = createHashHistory({ window, v5Compat: true }); } let history = historyRef.current; let [state, setState] = React.useState({ action: history.action, location: history.location, }); React.useLayoutEffect(() => history.listen(setState), [history]); return ( <Router basename={basename} children={children} location={state.location} navigationType={state.action} navigator={history} /> ); }

The Hashrouter instantiates and maintains it's own history reference internally. Hashrouter在内部实例化并维护它自己的history引用。 To fix the ref issue simply remove the ref.要解决 ref 问题,只需删除 ref。

const container = document.getElementById('root');
const root = createRoot(container);

root.render(
  <React.StrictMode>
    <ErrorBoundary>
      <Provider store={store}>
        <HashRouter>
          <GlobalStyles />
          <App />
        </HashRouter>
      </Provider>
    </ErrorBoundary>
  </React.StrictMode>
);

If you have need to use a custom hashHistory object then use the HistoryRouter and follow the instructions for rendering.如果您需要使用自定义hashHistory object,请使用HistoryRouter并按照说明进行渲染。 Be sure to have history@5 installed as a project dependency.确保将history@5安装为项目依赖项。

Issue 2第 2 期

Matched leaf route at location "/" does not have an element.位置“/”的匹配叶路由没有元素。 This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.这意味着默认情况下它将呈现具有 null 值的<Outlet /> ,从而导致“空”页面。

The Route component API changed significantly from v5 to v6. Route组件 API 从 v5 到 v6 发生了显着变化。 The Route components render all their content on a single element prop taking a React.ReactNode , aka JSX. Route组件将其所有内容呈现在采用React.ReactNode (又名 JSX)的单个element prop 上。 exact is no longer used now that all routes are always exactly matched.现在不再使用exact ,因为所有路由总是完全匹配的。

{routes
  .map((route, i) => {
    const Container = route.private ? AppContainer : AuthContainer;
    const Component = appInitialized
      ? googleAnalyticsRegisterView(route.component)
      : route.component;
    return (
      <Route
        key={i}
        path={route.path}
        element={(
          <Container key={i}>
            <Component />
          </Container>
        )}
      />
    )
  })
}

This also means there are no longer any route props.这也意味着不再有任何路线道具。 The routed components need to use the React hooks:路由组件需要使用 React 钩子:

  • useNavigate to access the navigate function that replaced history使用Navigate 访问替换historynavigate useNavigate
  • useParams to access path params that replaced match.params useParams访问替换match.params的路径参数
  • useLocation to access the location object使用Location访问location useLocation

Issue 3第 3 期

Uncaught Error: [Navigate] is not a <Route> component.未捕获的错误:[Navigate] 不是<Route>组件。 All component children of <Routes> must be a <Route> or <React.Fragment> <Routes>的所有子组件必须是<Route><React.Fragment>

Only Route or React.Fragment are valid children of the Routes component.只有RouteReact.FragmentRoutes组件的有效子级。 If you want to redirect that Navigate component still needs to be rendered on a route.如果要重定向该Navigate组件,仍需要在路由上呈现。 Specify the replace prop so the navigation action is a REPLACE instead of a PUSH.指定replace属性,以便导航操作是 REPLACE 而不是 PUSH。

<Route
  path="*"
  element={<Navigate to={token ? '/home' : '/'} replace />}
/>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM