29

I have an app that uses a react-router-config and uses a wrapper component to redirect unauthenticated users.

I have some functionality that requires the use of the route /tasks/:id but I am unable to access the :id value to perform the necessary task lookup.

My routes.js:

import React from "react";
...
const Tasks = React.lazy(() => import("./Components/Tasks"));
...
const routes = [
  {
    path: "/tasks/edit/:id",
    name: "View Task",
    component: Tasks
  }
...
];
export default routes;

Then I have AuthenticatedRoute.js:

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

export default function AuthenticatedRoute({
  component: C,
  appProps,
  ...rest
}) {
  return (
    <Route
      {...rest}
      render={props =>
        appProps.isAuthenticated ? (
          <C {...props} {...appProps} />
        ) : (
          <Redirect
            to={`/login?redirect=${props.location.pathname}${props.location.search}`}
          />
        )
      }
    />
  );
}

and in the App.js:

import React, { useState, useEffect } from "react";
import { BrowserRouter, Switch, withRouter } from "react-router-dom";
import AuthenticatedRoute from "./components/AuthenticatedRoute/AuthenticatedRoute";
import routes from "./routes";

function App(props) {
...
  return (
    <BrowserRouter>
      <React.Suspense fallback={loading()}>
        <Switch>
            {routes.map((route, idx) => {
              return route.component ? (
                <AuthenticatedRoute
                  key={idx}
                  path={route.path}
                  exact={route.exact}
                  name={route.name}
                  appProps={props}
                  component={props => <route.component {...props} />}
                />
              ) : null;
            })}
        </Switch>
...
      </React.Suspense>
    </BrowserRouter>

Finally I have my Tasks.js:

import React, { useState, useEffect } from "react";
...
function Tasks(props) {
  useEffect(() => {
    onLoad();
  }, []);

  const onLoad = async () => {
    console.log(JSON.stringify(props.match.params));
  };
...

Browsing to localhost:3000/tasks/1. props.match.params is empty in every component up the chain. props.match.params.id is undefined. I have also tried match with {match.params.id} but that is also undefined at every component.

I can see props.location.pathname but that is the full path and I would have to manually get the last segment. I can't get it to automatically grab the :id from the url.

EDIT Turns out my example was too simplified, which actually helped me identify the issue. In my previous version when I had the route:

  {
    path: "/tasks/:id",
    name: "View Task",
    component: Tasks
  }

everything actually works fine using useParams I am able to get the :id value. What I actually have in my app and what seems to be breaking it is adding an additional directory to the path:

  {
    path: "/tasks/edit/:id",
    name: "View Task",
    component: Tasks
  }

I am not sure how this makes a difference, but having the extra /edit seems to break the useParams

an00b
  • 442
  • 1
  • 4
  • 8

2 Answers2

52

react-router-dom provides some handy hooks. In your case, I'd suggest useParams() (link to docs)

import { useParams } from 'react-router-dom';

function MyComponent {
  let { id } = useParams();

  return (<p>{id}</p>);
}

I'd also probably opt for not using withRouter if you will be using the hooks provided by React Router

xyhhx
  • 6,384
  • 6
  • 41
  • 64
  • Thanks. I updated my question. I created a new page in my app with only a single base path `/test/:id` and your code worked. When I added the extra directory (which is what my app actually has) `/test/edit/:id` It no longer works. – an00b Nov 19 '19 at 00:43
  • [Check out this sandbox](https://codesandbox.io/s/quizzical-yalow-ydl9x): `let { id } = useParams()` _should_ work – xyhhx Nov 20 '19 at 22:25
  • my path is /student/general/request/:id. i can get the id but i need to check whether it's general or not. any way to capture that general part of path ? – Nipun Ravisara Apr 09 '20 at 01:56
  • 1
    @Ravisara You can use [`useLocation()`](https://reacttraining.com/react-router/web/api/Hooks/uselocation) – xyhhx Apr 09 '20 at 02:18
  • Thanks. with my code it can be done with " let { id, type } = useParams()" – Nipun Ravisara Apr 09 '20 at 03:02
  • That will work if your path is like `/student/:type/request/:id` – xyhhx Apr 09 '20 at 03:14
2

Martins answer is correct and works for functional components. I had the same problem, however I was using a class component (class Company extends Component) and Martins answer did not work for that case. If you have a class component you can do:

import { withRouter } from 'react-router-dom'

class MyComponent extends React.Component {

    componentDidMount() {
        // here you have the id
        const id = this.props.match.params.id;
      }

}

export default withRouter(MyComponent);
jakobinn
  • 1,832
  • 1
  • 20
  • 20