Why my custom route does not pass the right path to the child component?

Viewed 36

I have these routes inside my App.js file:

<LoggedInRoute userData={userData} path="/code/:id/view">
  <ViewCode />
</LoggedInRoute>
<LoggedInRoute userData={userData} path="/code/feed/:userId">
  <CodeFeed />
</LoggedInRoute>
// userData -> state representing user session info

Here it is my LoggedInRoute component:

import React from 'react';

import { Redirect, Route } from "react-router-dom";

function LoggedInRoute(props) {
  return (
    <Route
      render={({ location }) =>
        props.userData.id ? (
          props.children
        ) : (
          <Redirect
            to={{
              pathname: "/login",
              state: { from: location }
            }}
          />
        )
      }
    />
  );
}

export default LoggedInRoute;

I am using the useParams() function (from react-router-dom) to extra id and userId in my child components ViewCode and CodeFeed. now the problem is that are extracted as undefined values. If I use Route instead of LoggedInRoute, everything works fine. It seems like my LoggedInRoute component doesn't pass the path to the child components. I checked the location object, from useLocation(), and the path it's the right one in the child components. What I am missing? Thanks in advance, guys :)

3 Answers

This solved my issue:

import React from 'react';

import { Redirect, Route } from "react-router-dom";

function LoggedInRoute(props) {
  return (
    <Route
      path={props.path}
      render={({ location }) =>
        props.userData.id ? (
          props.children
        ) : (
          <Redirect
            to={{
              pathname: "/login",
              state: { from: location }
            }}
          />
        )
      }
    />
  );
}

export default LoggedInRoute;

The issue here is that you are not passing through any routing props to the Route component you are rendering. No path prop means it can't match URL paths.

function LoggedInRoute(props) {
  return (
    <Route
      // <-- missing is any path or any other route prop
      render={({ location }) =>
        props.userData.id ? (
          props.children
        ) : (
          <Redirect
            to={{
              pathname: "/login",
              state: { from: location }
            }}
          />
        )
      }
    />
  );
}

To resolve you can destructure the custom props and spread the rest into the Route component.

function LoggedInRoute({ children, userData, ...props }) {
  return (
    <Route
      {...props} // path and other non-destructered props spread in
      render={({ location }) =>
        userData.id ? (
          children
        ) : (
          <Redirect
            to={{
              pathname: "/login",
              state: { from: location }
            }}
          />
        )
      }
    />
  );
}

But this code could be improved. Instead of rendering a Route and trying to do all the logic in the render prop function, move the logic into the LoggedInRoute component body and conditionally render a Route with all the route props passed through, or the redirect.

function LoggedInRoute({ userData, ...props }) {
  const location = useLocation();

  return userData?.id ? (
    <Route {...props} />
  ) : (
    <Redirect
      to={{
        pathname: "/login",
        state: { from: location }
      }}
    />
  );
}

You should spread the props to the Route component that you wrap with your custom one.

import { Redirect, Route } from "react-router-dom";

function LoggedInRoute(props) {
  return (
    <Route
      ...props
      render={({ location }) =>
        props.userData.id ? (
          props.children
        ) : (
          <Redirect
            to={{
              pathname: "/login",
              state: { from: location }
            }}
          />
        )
      }
    />
  );
}

export default LoggedInRoute;
Related