ziad hany
ziad hany

Reputation: 353

React-router-v6 access a url parameter

How can i access url parameter in my react component ?

App.js

<Route path="/question/:id" element={<QuestionView />} />

QuestionView.js

class QuestionView extends React.Component {     
    render() {
          const { questions, users } = this.props;
          const {id} = ??? 

Upvotes: 21

Views: 33526

Answers (8)

Azus5
Azus5

Reputation: 3

You can use useParams from react-router-dom, as follows:

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

function GenericShowPage() {
  const { id } = useParams();

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

export default GenericShowPage

Upvotes: 0

Saeed Farahi
Saeed Farahi

Reputation: 121

This is what I wrote in typescript

import React from 'react'
import { Location, NavigateFunction, Params, useLocation, useNavigate, useParams } from 'react-router-dom'

export function withRouter<P extends { router: RouterInfo }>(
  Component: React.ComponentType<P>,
): React.ElementType<Omit<P, 'router'>> {
  function ComponentWithRouterProp(props: any) {
    const location = useLocation()
    const navigate = useNavigate()
    const params = useParams()
    return <Component {...props} router={{ location, navigate, params }} />
  }

  return ComponentWithRouterProp
}

interface RouterInfo {
  location: Location
  navigate: NavigateFunction
  params: Params
}

export interface WithRouter {
  router: RouterInfo
}

The Omit<P, 'router'> is there to remove router property from required props.

Your component:

interface Props extends WithRouter {
  ...component properties
}

export class MyComponent extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props)
    
    // access route:
    console.log(this.props.router.params.someEntityIdMaybe)
  }
  ....
}

Upvotes: 0

greg-e
greg-e

Reputation: 434

I had a similar issue, described here: Params from React Router with class components and typescript

I created a new functional component, so I can use useParams():

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

type Props = {
    children: JSX.Element
};

export const WithParams: React.FC<Props> = (props) => {
    const params = useParams();
    return React.cloneElement(props.children, {...props.children.props, ...params });
}; 

and added it to my Route.element

<Route path="/Contacts/VerifyEmailAddress/:id" 
  element={
    <WithParams>
      <VerifyEmail />
    </WithParams>
  }>
</Route>

and added the parameters I need to the props of my child component.

export class VerifyEmailProps {
    public id?: string;
}

Upvotes: 0

Fatih Ertuğral
Fatih Ertuğral

Reputation: 347

TypeScript version

withRouter.tsx

import React from 'react';
import {
    useLocation,
    useNavigate,
    useParams,
    NavigateFunction,
    Params,
    Location,
} from 'react-router-dom';

export interface RouterProps {
    router: {
        navigate: NavigateFunction;
        readonly params: Params<string>;
        location: Location;
    }
}

function withRouter(Component: React.ComponentType<RouterProps>) {
    const ComponentWithRouterProp: React.FC = () => {
        const location = useLocation();
        const navigate = useNavigate();
        const params = useParams();

        return (
            <Component
                router={{ location, navigate, params }}
            />
        );
    };

    return ComponentWithRouterProp;
}

export default withRouter;

MyComponent.tsx

import React from 'react';
import { connect } from 'react-redux';

import { RootState } from '@@/redux/store';
import {
    addSettings,
    updateSettings,
} from '@@/redux/mySlice';
import withRouter, { RouterProps } from '@@/withRouter';

const mapState = (state: RootState) => ({
    myStore: state.variation.myStore,
});

const mapDispatch = {
    addSettings,
    updateSettings,
};

type IProps = ReturnType<typeof mapState> & typeof mapDispatch & RouterProps;

class MyComponent extends React.Component<IProps> {
    constructor(props: IProps) {
        super(props);
    }

    onNavigateHome = () => {
        this.props.router.navigate('/');
    }

    render(): React.ReactNode {
        return (
            <div className="test" onClick={this.onNavigateHome}>test</div>
        );
    }
}

export default withRouter(connect(mapState, mapDispatch)(MyComponent));

Upvotes: 0

Arefe
Arefe

Reputation: 12471

If you would like to use a class, then you will need to wrap it with the withRouter. I provide an example below:

This is my class for the movie form:

class MovieForm extends Form {

    state = {
        data: {
            title: "",
            genreId: "",
            numberInStock: "",
            dailyRentalRate: ""
        },
        genres: [],
        errors: {}
    };

    schema = {
        _id: Joi.string(),
        title: Joi.string()
            .required()
            .label("Title"),
        genreId: Joi.string()
            .required()
            .label("Genre"),
        numberInStock: Joi.number()
            .required()
            .min(0)
            .max(100)
            .label("Number in Stock"),
        dailyRentalRate: Joi.number()
            .required()
            .min(0)
            .max(10)
            .label("Daily Rental Rate")
    };




    componentDidMount() {

        const genres = getGenres();
        this.setState({ genres });

        // const movieId = this.props.match.params.id;
        const movieId = this.props.params.id;
        if (movieId === "new") return;

        const movie = getMovie(movieId);
        if (!movie) return this.props.history.replace("/not-found");

        this.setState({ data: this.mapToViewModel(movie) });
    }

    mapToViewModel(movie) {
        return {
            _id: movie._id,
            title: movie.title,
            genreId: movie.genre._id,
            numberInStock: movie.numberInStock,
            dailyRentalRate: movie.dailyRentalRate
        };
    }


    doSubmit = () => {
        saveMovie(this.state.data);
        this.props.navigate("/movies");
    };



    render() {

        return (
            <div>
                <h1>Movie Form</h1>
                <form onSubmit={this.handleSubmit}>
                    {this.renderInput("title", "Title")}
                    {this.renderSelect("genreId", "Genre", this.state.genres)}
                    {this.renderInput("numberInStock", "Number in Stock", "number")}
                    {this.renderInput("dailyRentalRate", "Rate")}
                    {this.renderButton("Save")}
                </form>
            </div>
        );
    }
}

I write a wrapper outside of the class:

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

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

Now, at the end of the file I will export it like below:

export default withRouter(MovieForm);

Insdie the withRouter, I get all the functions that I will use later inside the class:

const params = useParams();
const navigate = useNavigate();

Upvotes: 1

imjoymhnt
imjoymhnt

Reputation: 1091

Here's the code example I'm using in my project to get the id from the URL:

import React from 'react'
import {Button} from 'antd'
import {useParams} from 'react-router-dom'

const DeleteUser = () => {

    const {id} = useParams()
const handleDelete = async () => {
        // handle delete function
    }


  return (
    <Button onClick={handleDelete}>Delete User</Button>
  )
}

export default DeleteUser

Upvotes: 1

egor.xyz
egor.xyz

Reputation: 3197

HOC withRouter TypeScript version with generic Params

withRouter.tsx

import { ComponentType } from 'react';
import { useLocation, useNavigate, useParams } from 'react-router-dom';

export interface WithRouterProps<T = ReturnType<typeof useParams>> {
  history: {
    back: () => void;
    goBack: () => void;
    location: ReturnType<typeof useLocation>;
    push: (url: string, state?: any) => void;
  }
  location: ReturnType<typeof useLocation>;
  match: {
    params: T;
  };
  navigate: ReturnType<typeof useNavigate>;
}

export const withRouter = <P extends object>(Component: ComponentType<P>) => {
  return (props: Omit<P, keyof WithRouterProps>) => {
    const location = useLocation();
    const match = { params: useParams() };
    const navigate = useNavigate();

    const history = {
      back: () => navigate(-1),
      goBack: () => navigate(-1),
      location,
      push: (url: string, state?: any) => navigate(url, { state }),
      replace: (url: string, state?: any) => navigate(url, {
        replace: true,
        state
      })
    };

    return (
      <Component
        history={history}
        location={location}
        match={match}
        navigate={navigate}
        {...props as P}
      />
    );
  };
};

MyClass.tsx

import { Component } from 'react';

import { withRouter, WithRouterProps } from './withRouter';

interface Params {
  id: string;
}

type Props = WithRouterProps<Params>;

class MyClass extends Component<Props> {
  render() {
    const { match } = this.props;
    console.log(match.params.id); // with autocomplete
    return <div>MyClass</div>;
  }
}

export default withRouter(MyClass);

Upvotes: 11

Drew Reese
Drew Reese

Reputation: 203476

Issue

In react-router-dom v6 the Route components no longer have route props (history, location, and match), and the current solution is to use the React hooks "versions" of these to use within the components being rendered. React hooks can't be used in class components though.

To access the match params with a class component you must either convert to a function component, or roll your own custom withRouter Higher Order Component to inject the "route props" like the withRouter HOC from react-router-dom v5.x did.

Solution

I won't cover converting a class component to function component. Here's an example custom withRouter HOC:

const withRouter = WrappedComponent => props => {
  const params = useParams();
  // etc... other react-router-dom v6 hooks

  return (
    <WrappedComponent
      {...props}
      params={params}
      // etc...
    />
  );
};

And decorate the component with the new HOC.

export default withRouter(Post);

This will inject a params prop for the class component.

this.props.params.id

Upvotes: 38

Related Questions