I'm trying to use the useLocation
hook in my component:
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));
And I place this component here:
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);
So then I get an error:
Uncaught Error: useLocation() may be used only in the context of a <Router> component.
But I don't understand how can I do such thing if RouterProvider
component does not accept any children of something like a component to inject it.
I need this EDProgressBar
component to exist in the application regardless of the active route. I need it to exist in every page.
How can I do so?
This is my 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;
Note that I removed some routes because they are irrelevant here, but the important thing is that I use the loader
key in some, so I need this builder.
The useLocation
and other RRD hooks can't be used outside any routing context provided by a router. I suggest creating a layout route that renders the EDNotification
and EDProgressBar
components and the backdrop and overlay divs. This layout route will be included in the routes returned by RouterBuilder
. This allows the components accessing the routing context to have the router higher than them in the ReactTree.
Example:
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>
);
};