We are looking to add custom logging to our react application, and would like to log each time a user changes routes. To handle this, we are creating a wrapper component , and this is what we currently have:
import React, { useEffect } from 'react';
import { Route } from 'react-router-dom';
function LoggerRoute(isExact, path, element) {
useEffect(() => {
// send route-change log event to our mongodb collection
}, []);
// And return Route
return (
isExact
? <Route exact path={path} element={element} />
: <Route exact path={path} element={element} />
);
}
export default LoggerRoute;
...and in our App.js file, we have changed the routes as such:
// remove this // <Route exact path='/tools/team-scatter' element={<TeamScatterApp />} />
<LoggerRoute isExact={true} path='/tools/team-scatter' element={<TeamScatterApp />} />
However, this throws the error Uncaught Error: [LoggerRoute] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>
.
Additionally, it feels off passing props to a route, if possible we would prefer
<LoggerRoute exact path='/tools/team-scatter' element={<TeamScatterApp />} />
as the ideal way to call our LoggerRoute. We like the idea of a wrapper component, this way we don’t have to add logging into every component that our app routes to. However, I’m not sure if this wrapper component approach is possible if can only accept a component. How can we modify our LoggerRoute
component to work / be better?
CodePudding user response:
In react-router-dom@6
only Route
and React.Fragment
are valid children of the Routes
component. Create either a wrapper component or a layout route component to handle listening for route path changes. Since you presumably want to do this for more than one route at-a-time I suggest the layout route method, but you can create a component that handles either.
Example:
import { Outlet, useLocation } from 'react-router-dom';
const RouteListenerLayout = ({ children }) => {
const { pathname } = useLocation();
useEffect(() => {
// send route-change log event to our mongodb collection
}, [pathname]);
return children ?? <Outlet />;
};
The children
prop is used in the cases where you want to wrap individual routed components (i.e. the element
prop) or an entire component, (*i.e. App
) that is rendering a set of Routes
. The Outlet
component is used in the case where you want to conditionally include a subset of routes within a Routes
component (i.e. some nested Route
components).
Wrap the routes you want to listen to route changes for.
Examples:
<Routes>
<Route element={<RouteListenerLayout />}>
<Route path="path1" element={<SomeComponent />} />
<Route path="someOtherPath" element={<SomeOtherComponent />} />
... wrapped routes components with listener
</Route>
... routes w/o listener
</Routes>
or
<Routes>
<Route
path="/"
element={(
<RouteListenerLayout>
<SomeComponent />
</RouteListenerLayout>
)}
/>
</Routes>
or
<RouteListenerLayout>
<App />
</RouteListenerLayout>
These all assume the router is rendered higher in the ReactTree than RouteListenerLayout
so the useLocation
hook works as expected.
CodePudding user response:
In v6, <Route>
is a lot more strict than it was in v5. Instead of building wrappers for , it may be used only inside other <Routes>
or <Route>
elements. If you try to wrap a <Route>
in another component it will never render.
What you should be doing instead is adding a wrapper component and leveraging it in the element
prop on the route.
function App() {
return (
<Routes>
<Route path="/public" element={<PublicPage />} />
<Route
path="/route"
element={
<AddLogging>
<YourPage/>
</AddLogging>
}
/>
</Routes>
);
}
Edit: here is an example wrapper component based on your needs:
function AddLogging({children}) {
useEffect(() => {
// send route-change log event to our mongodb collection
// can use useLocation hook to get route to log
}, []);
return children;
}