React-router-v6 accede a un parámetro de URL
¿Cómo puedo acceder al parámetro de URL en mi componente de reacción?
aplicación.js
<Route path="/question/:id" element={<QuestionView />} />
PreguntaView.js
class QuestionView extends React.Component {
render() {
const { questions, users } = this.props;
const {id} = ???
Asunto
En react-router-dom v6, los Route
componentes ya no tienen accesorios de ruta ( history
, location
y match
), y la solución actual es usar las "versiones" de los ganchos de React para usar dentro de los componentes que se procesan. Sin embargo, los ganchos de React no se pueden usar en componentes de clase.
Para acceder a los parámetros de coincidencia con un componente de clase, debe convertirlo a un componente de función o ejecutar su propio withRouter
componente de orden superior personalizado para inyectar los "accesorios de ruta" como lo hizo el withRouter
HOC de v5.x.react-router-dom
Solución
No cubriré la conversión de un componente de clase en un componente de función. Aquí hay un ejemplo withRouter
de HOC personalizado:
const withRouter = WrappedComponent => props => {
const params = useParams();
// etc... other react-router-dom v6 hooks
return (
<WrappedComponent
{...props}
params={params}
// etc...
/>
);
};
Y decora el componente con el nuevo HOC.
export default withRouter(Post);
Esto inyectará un params
accesorio para el componente de clase.
this.props.params.id
withRouter
Versión HOC TypeScript con parámetros genéricos
conRouter.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}
/>
);
};
};
MiClase.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);
Si desea utilizar una clase, deberá envolverla con el archivo withRouter
. Proporciono un ejemplo a continuación:
Esta es mi clase para la forma de película:
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>
);
}
}
Escribo un contenedor fuera de la clase:
const withRouter = WrappedComponent => props => {
const params = useParams();
const navigate = useNavigate();
return (
<WrappedComponent
{...props}
params={params}
navigate={navigate}
/>
);
};
Ahora, al final del archivo lo exportaré como se muestra a continuación:
export default withRouter(MovieForm);
Dentro de withRouter
, obtengo todas las funciones que usaré más adelante dentro de la clase:
const params = useParams();
const navigate = useNavigate();