49
votes

I'm trying to load a details view based on a react-router-dom route that should grab the URL parameter (id) and use that to further populate the component.

My route looks like /task/:id and my component loads fine, until I try to grab the :id from the URL like so:

import React from "react";
import { useParams } from "react-router-dom";

class TaskDetail extends React.Component {
    componentDidMount() {
        let { id } = useParams();
        this.fetchData(id);
    }

    fetchData = id => {
        // ...
    };

    render() {
        return <div>Yo</div>;
    }
}

export default TaskDetail;

This triggers the following error and I'm unsure where to correctly implement useParams().

Error: Invalid hook call. Hooks can only be called inside of the body of a function component.

The docs only show examples based on functional components, not class based.

Thanks for your help, I'm new to react.

6
Hooks don't work in class based componentsDupocas
@Dupocas ok that makes sense. What would you suggest, rewrite the class to a function or use a class and try to grab the url parameter some other way?Jorre
Both valid alternatives. Can you post the code for useParams? Maybe turn it into an HOC?Dupocas
Can anyone comment on why this is limited to function components? I've been using React for maybe 8 months and things like this are still a regular "gotcha" for me, would love to understand it better.BobRz

6 Answers

92
votes

You can use withRouter to accomplish this. Simply wrap your exported classed component inside of withRouter and then you can use this.props.match.params.id to get the parameters instead of using useParams(). You can also get any location, match, or history info by using withRouter. They are all passed in under this.props

Using your example it would look like this:

import React from "react";
import { withRouter } from "react-router";

class TaskDetail extends React.Component {
    componentDidMount() {
        const id = this.props.match.params.id;
        this.fetchData(id);
    }

    fetchData = id => {
        // ...
    };

    render() {
        return <div>Yo</div>;
    }
}

export default withRouter(TaskDetail);

Simple as that!

9
votes

Params get passed down through props on the match object.

props.match.params.yourParams

source: https://redux.js.org/advanced/usage-with-react-router

Here is an example from the docs destructing the props in the arguments.

const App = ({ match: { params } }) => {
  return (
    <div>
      <AddTodo />
      <VisibleTodoList filter={params.filter || 'SHOW_ALL'} />
      <Footer />
    </div>
  )
}
2
votes

You can not call a hook such as "useParams()" from a React.Component.

Easiest way if you want to use hooks and have an existing react.component is to create a function then call the React.Component from that function and pass the parameter.

import React from 'react';
import useParams from "react-router-dom";

import TaskDetail from './TaskDetail';

function GetId() {

    const { id } = useParams();
    console.log(id);

    return (
        <div>
            <TaskDetail taskId={id} />
        </div>
    );
}

export default GetId;

Your switch route will still be something like

<Switch>
  <Route path="/task/:id" component={GetId} />
</Switch>

then you will be able toe get the id from from the props in your react component

this.props.taskId
2
votes

Since hooks wont work with class based components you can wrap it in a function and pass the properties along:

class TaskDetail extends React.Component {
    componentDidMount() {
        const { id } = this.props.params;
        // ...
    }
}

export default (props) => (
    <TaskDetail
        {...props}
        params={useParams()}
    />
);

But, like @michael-mayo said, I expect this is what withRouter is already performing.

2
votes

React Route v5

Query params can be read and processed as JSON using withRouter and queryString as follow:

import React from "react";
import { withRouter } from "react-router";
import queryString from 'query-string';
    
class MyComponent extends React.Component {
    componentDidMount() {
        const params = queryString.parse(this.props.location.search);
        console.log('Do something with it', params);
    }

    render() {
        return <div>Hi!</div>;
    }
}

export default withRouter(MyComponent);
-5
votes

With react router 5.1 you can get the id with following:

<Switch>
  <Route path="/item/:id" component={Portfolio}>
    <TaskDetail />
  </Route>
</Switch>

and your component than can access id:

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

const TaskDetail = () => {
  const {id} = useParams();
    return (
      <div>
        Yo {id}
      </div>
    );
};