reactjsreact-router-domreact-component

Creating wrapper component for react-router-dom v6 Route that logs route changes


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?


Solution

  • 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.