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} = ???
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
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);
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();
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