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. 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. 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();
I know there is something about that ref property or HashRouter but in the documentation I can't find it.
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
This does indeed appear to be related to the HashRouter
component. You can look at the type declaration and see that the HashRouter
doesn't consume a ref
prop.
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.
/** * 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. To fix the ref issue simply remove the 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. Be sure to have history@5
installed as a project dependency.
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.
The Route
component API changed significantly from v5 to v6. The Route
components render all their content on a single element
prop taking a React.ReactNode
, a.k.a. JSX. exact
is no longer used now that all routes are always exactly matched.
{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:
useNavigate
to access the navigate
function that replaced history
useParams
to access path params that replaced match.params
useLocation
to access the location
objectUncaught Error: [Navigate] is not a
<Route>
component. All component children of<Routes>
must be a<Route>
or<React.Fragment>
Only Route
or React.Fragment
are valid children of the Routes
component. If you want to redirect that Navigate
component still needs to be rendered on a route. Specify the replace
prop so the navigation action is a REPLACE instead of a PUSH.
<Route
path="*"
element={<Navigate to={token ? '/home' : '/'} replace />}
/>