84

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.

5
  • 17
    Hooks don't work in class based components Commented Oct 24, 2019 at 20:32
  • 2
    @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? Commented Oct 24, 2019 at 20:47
  • Both valid alternatives. Can you post the code for useParams? Maybe turn it into an HOC? Commented Oct 25, 2019 at 10:58
  • 9
    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. Commented Jul 2, 2020 at 17:49
  • For function in React Route v6 : stackoverflow.com/a/70251443/624533 Commented Dec 6, 2021 at 20:23

17 Answers 17

116

Version <= 5:

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!

Sign up to request clarification or add additional context in comments.

7 Comments

I got an error when use withRouter in react router dom v6: Attempted import error: 'withRouter' is not exported from 'react-router-dom'
Yea it looks like v6 is not using it anymore. You can still import it from react-router. I will update my answer to reflect this
react-router do not contain withRouter too. I got a same error result
here is the docs for using it. maybe this will help a littl better
this is no longer a valid solution - withRouter is not available in react-router 6.
|
35
import React, { Component } from "react";
import { useParams } from "react-router-dom";

function withParams(Component) {
  return props => <Component {...props} params={useParams()} />;
}


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

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

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

export default withParams(TaskDetail);

4 Comments

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
Really helped me, wrapping the class component in a function to work as a custom HOC is the key
Well, thanks a lot, literally only one solution I have found that works for me...
this is actually the most genius way to workaround react-router 6's stupidity, +1
19

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.

3 Comments

thank you for this - withRouter is no longer an option since it is removed from react-router ^6. This solution works and gives me more time before I need to deprecate large class based components.
Oh yeah, they did go full in on hooks recently. I'm glad this will help keep some class based components running
This should be selected as the answer.
10

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>
  )
}

1 Comment

This seems to be a very clean solution to avoid hooks but is there a caveat? Why do the react-router docs do not mention this as an alternative and only show the hooks example in the documentation?
6

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 to get the id from from the props in your react component

this.props.taskId

2 Comments

one liner Route with Component works, rather <Route>code</Route> not works why so?
It should be { useParams } instead of useParams in the import statement.
5

In react-router-dom-v6 you can easily use useParams() in functional components but when it gets to the class component you have to create HOC (higher-order component) because hooks don't support class components:

import { useNavigate, useParams } from "react-router-dom";

export const withRouter = (WrappedComponent) => (props) => {
  const params = useParams();
  const navigate = useNavigate();

  return <WrappedComponent {...props} params={params} navigate={navigate} />;
};

Then export your component from your HOC and give your component as a parameter. like below:

export default withRouter(YourComponentName);

After that you can easily access the url id with this.props.params.id and you can navigate to other components with this.props.navigate("/YourPath")

Comments

3

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);

Comments

1

SmujMaiku is rigth!!! His answer works perfectly. This is how work today with react-router v6

enter code here
   
   import React ,{Component} from 'react'
   import {  useParams } from "react-router-dom";
  import PokeDescription from '../components/PokeDescription'

 class PokeInfoConteiner extends Component{

 render(){
    
    let urlPokemon= "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/"
    
    
    const {idPokemon} = this.props.params 
    console.log(idPokemon)

    return(
        
        <div>
            <PokeDescription pokeImage={`${urlPokemon}${idPokemon}.png?raw=true`}/>
            <p>{}</p>
            
        </div>

    )

}

}

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

Comments

1

in React Router V6 :

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

/* This is a higher order component that 
*  inject a special prop   to our component.
*/ 
function withRouter(Component) {
  function ComponentWithRouter(props) {
    let params = useParams()
    return <Component {...props} params={params} />
  }
  return ComponentWithRouter
}
class TaskDetail extends React.Component {
    state={
      id : ""
    }
    componentDidMount() {
      this.setState({
        id : this.props.params.id
      })
    }
    static getDerivedStateFromProps(nextProps) {
      return {
        id : nextProps.params.id
      }
    }
    fetchData = id => {
        // ...
    };

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


const HOCTaskDetail = withRouter(TaskDetail);

export default HOCTaskDetail;

Comments

0

React Route v6

My friends, I tried to use in class but I failed to find any doc about it. So after many hours of searching and trying hard this is (in function). Now (i.e when I'm writing this post) there is only limited resource about v6. But there are many for <v6.

Here I'm using useState,useEffect,useParams,axios.

import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';

const Post = () => {
    let { post_id } = useParams();
    const [posts, setPosts] = useState({ post: null, countSecrets: 0, ui: '' });

    useEffect(() => {
        if (posts.countSecrets === 0) {
            const doAxe = (a) => {
                axios.get('https://jsonplaceholder.typicode.com/posts/' + post_id)
                    .then((res) => {
                        setPosts(s => ({ ...s, value: res.data }));
                        doUI(res.data)
                        // console.log(res.data)
                    });
            }
            setPosts(s => ({ ...s, countSecrets: s.countSecrets + 1 }));
            doAxe()
        }
    }, [posts, post_id]);
    let doUI = (x) => {
        // console.log('x' + x.title)
        const finalPost = (x !== null) ? (
            <div className="post">
                <h4 className="center">{x.title}</h4>
                <p>{x.body}</p>
            </div>
        ) : (
            <div className="center">Loading posts...</div>
        );
        setPosts(s => ({ ...s, ui: finalPost }));
    }
    return (
        <div className="container">
            {posts.ui}
        </div>
    );
}

export default Post;

NOTE: I faced useEffect looping. I prevented it with a key.

HOPE: This may help someone!

Reference:

Comments

0

In react-router-dom v6, there is no hook such as withRouter therefore my advice to you is to convert your class-based component to a functional component to use useParams hook in your component otherwise you can create a higher-order component to pass your class-based component.

4 Comments

...here's an example of how it can be done: Edit react-router-v5-to-v6-compatibility
this is quite painful on large components!
@k90mirzaei Your solution worked nice!
0

as you know the useParams() is a hook for react-router-dom. you can not use this inside the componentDidMount() or useEffect() because both of them are method that called during the Mounting phase of the React Life-cycle i.e after the component is rendered. you have a solution: create or define another function outside the componentDidMount() to define useParams then call it inside the componentDidMount. know every thing will be ok.

Comments

0

This is my working example. :)

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

function withParams(Component) {
  return (props) => <Component {...props} params={useParams()} />;
}

class ProductDetails extends Component {
  handleSave = () => {
    // Navigate to /products
  };

  render() {
    return (
      <div>
        <h1>Product Details - {this.props.params.id}</h1>
        <button onClick={this.handleSave}>Save</button>
      </div>
    );
  }
}

export default withParams(ProductDetails);

Comments

0

Hooks only work on functional components, you have to make that ocmponent a functional component

Comments

0

Fixed by creating a wrapping function

I needed to pass params to my SaxjaxApp.js from index.js using react-router-dom v6. In v6 Switch has been changed to Routes

I got the useParams working with a class component by following Mohamed MAZEK's idea in post 20 using a wrapping function.

I needed to access the sessionId part of the url when it was available.

ie in localhost:3000/shared/123XYZId I needed the 123XYZId part.

make note of this line : <Route path="/shared/:sessionId" element={<SaxjaxAppWrapper />} /> in the index.js below.

:sessionId denotes that useParams has a property called sessionId, that can be accessed by:

const {sessionId} = useParams() from a functional component.

In my index.js file I did this:

import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter, Route, Routes } from "react-router-dom";

import "./styles/style.scss";

import SaxjaxAppWrapper from "SaxjaxAppWrapper";
import SaxjaxApp from "./SaxjaxApp";

const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
//INFO: to learn about react-roue-dom v6 https://reactrouter.com/en/v6.3.0/upgrading/v5

root.render(
  // <React.StrictMode>
  <BrowserRouter>
    <Routes>
      <Route path="/shared/:sessionId" element={<SaxjaxAppWrapper />} />
      <Route path="/" element={<SaxjaxApp />} />
    </Routes>
  </BrowserRouter>
  // </React.StrictMode>
);

This line <Route path="/shared/:sessionId" element={<SaxjaxAppWrapper />} /> calls my wrapping function, whereas the default path / just calls the class component.

I had to create a separate file to hold the wrapping function I don't know why:

import React from "react";
import { useParams } from "react-router-dom";
import SaxjaxApp from "SaxjaxApp";
    
 function SaxjaxAppWrapper() {

//I use the params here and store them to pass as props 
  let { sessionId } = useParams();

  return (
//I pass the sessionId from the url params as a prop to my SaxjaxApp class component here
      <SaxjaxApp sessionId={sessionId} />
  );
}
    
export default SaxjaxAppWrapper;

My class component:

import React, { Component } from "react";
import "./styles/style.scss";

class SaxjaxApp extends Component {
 state = {
   octave: 4,
 };

 constructor(props) {
   super(props);
   //... initialise stuff
 }

//... a lot of methods

render() {
//Access the param here
   const { sessionId } = this.props;
     <>
         <div>
           keybordId={sessionId ? sessionId : "no id was passed"}
         </div>
     </>

   );
 }
}

export default SaxjaxApp;

Comments

0

Errors because :

  • Hooks Used Only In Functional Component
  • In deceleration of the id value use const alternate of use let because you don't need to change the id value

Corrected code:

import React, { useEffect } from "react";
import { useParams } from "react-router-dom";
function TaskDetail() {
    const { id } = useParams();

    useEffect(() => {
        fetchData(id);
    }, [id]);

    const fetchData = (id) => {
        // ... Implement data fetching logic here
    };

    return <div>Yo</div>;
}

export default TaskDetail;

Comments

0

Ssssh, hack ahead

If you feel rebellious to React's whole "you have to use hooks because we decided it is a better way for you to maintain your code", here is a solution:

import {UNSAFE_RouteContext} from 'react-router-dom'
import React from 'react'

class FreedomToClassComponents extends React.Component {
    // seems like it is marked unsafe because it is internal: 
    //    https://github.com/remix-run/react-router/blob/13ac1b6c5cc2c3f99629bed6ed0ccc8542de0d94/packages/react-router/index.ts#L328
    static contextType = UNSAFE_RouteContext


    render() {
        // if this breaks, just check what they are doing at: 
        //    https://github.com/remix-run/react-router/blob/13ac1b6c5cc2c3f99629bed6ed0ccc8542de0d94/packages/react-router/lib/hooks.tsx#L293C17-L293C26
        let {matches} = this.context
        let routeMatch = matches[matches.length - 1];
        const params = routeMatch ? routeMatch.params : {};

        // say route is "/task/:id" and url is mywebsite.com/task/12, 
        // this will print '{id: 12}'
        console.log(params)

        return React.createElement("div", {}, "bonus: if you define elements like this you don't need jsx")
    }
}
export default FreedomToClassComponents;

bit more organized version:

// ComponentWithRouteParams.js
import {UNSAFE_RouteContext} from 'react-router-dom'
import React from 'react'
class ComponentWithRouteParams extends React.Component {
    static contextType = UNSAFE_RouteContext
    get route_params() {
        let {matches} = this.context
        let routeMatch = matches[matches.length - 1];
        const params = routeMatch ? routeMatch.params : {};
    }
}
export default ComponentWithRouteParams


// FreedomToClassComponents.js
import ComponentWithRouteParams from "./ComponentWithRouteParams"
import React from 'react'

class FreedomToClassComponents extends ComponentWithRouteParams {
    render() {
        console.log(this.route_params)
        return React.createElement("div", {}, "bonus: if you define elements like this you don't need jsx")
    }
}
export default FreedomToClassComponents;

if any maintainers come across this -- please support class components if/whenever you can, especially for basic functionalities like this if it's not a big maintenance cost. Users might have reasons/opinions for organizing their code in flexible ways, which should be allowed.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.