reactjs - react - Navegue mediante programación usando el router de reacción V4
react-router-dom redirect (14)
Como a veces prefiero cambiar las rutas por aplicación y luego por botones, este es un ejemplo de trabajo mínimo que funciona para mí:
import { Component } from ''react''
import { BrowserRouter as Router, Link } from ''react-router-dom''
class App extends Component {
constructor(props) {
super(props)
/** @type BrowserRouter */
this.router = undefined
}
async handleSignFormSubmit() {
await magic()
this.router.history.push(''/'')
}
render() {
return (
<Router ref={ el => this.router = el }>
<Link to="/signin">Sign in</Link>
<Route path="/signin" exact={true} render={() => (
<SignPage onFormSubmit={ this.handleSignFormSubmit } />
)} />
</Router>
)
}
}
Acabo de reemplazar
react-router
de v3 a v4.
Pero no estoy seguro de cómo navegar mediante programación en la función miembro de un
Component
.
es decir, en la función
handleClick()
quiero navegar a
/path/some/where
después de procesar algunos datos.
Solía hacer eso por:
import { browserHistory } from ''react-router''
browserHistory.push(''/path/some/where'')
Pero no puedo encontrar tales interfaces en v4.
¿Cómo puedo navegar usando v4?
Como no hay otra forma de lidiar con este horrible diseño, escribí un componente genérico que utiliza el enfoque
withRouter
HOC
.
El siguiente ejemplo está envolviendo un elemento de
button
, pero puede cambiar a cualquier elemento en el que pueda hacer clic:
import React from ''react'';
import PropTypes from ''prop-types'';
import { withRouter } from ''react-router-dom'';
const NavButton = (props) => (
<Button onClick={() => props.history.push(props.to)}>
{props.children}
</Button>
);
NavButton.propTypes = {
history: PropTypes.shape({
push: PropTypes.func.isRequired
}),
to: PropTypes.string.isRequired
};
export default withRouter(NavButton);
Uso:
<NavButton to="/somewhere">Click me</NavButton>
Creo que @rgommezz cubre la mayoría de los casos menos uno que creo que es bastante importante.
// history is already a dependency or React Router, but if don''t have it then try npm install save-dev history
import createHistory from "history/createBrowserHistory"
// in your function then call add the below
const history = createHistory();
// Use push, replace, and go to navigate around.
history.push("/home");
Esto me permite escribir un servicio simple con acciones / llamadas a las que puedo llamar para hacer la navegación desde cualquier componente que desee sin hacer mucho HoC en mis componentes ...
No está claro por qué nadie ha proporcionado esta solución antes. Espero que ayude, y si ve algún problema con esto, hágamelo saber.
Esto funciona:
import { withRouter } from ''react-router-dom'';
const SomeComponent = withRouter(({ history }) => (
<div onClick={() => history.push(''/path/some/where'')}>
some clickable element
</div>);
);
export default SomeComponent;
He estado probando v4 durante unos días y ... ¡hasta ahora me encanta! Simplemente tiene sentido después de un tiempo.
También tuve la misma pregunta y encontré que manejarlo como el siguiente funcionó mejor (e incluso podría ser cómo se pretende).
Utiliza state, un operador ternario y
<Redirect>
.
En el constructor ()
this.state = {
redirectTo: null
}
this.clickhandler = this.clickhandler.bind(this);
En el render ()
render(){
return (
<div>
{ this.state.redirectTo ?
<Redirect to={{ pathname: this.state.redirectTo }} /> :
(
<div>
..
<button onClick={ this.clickhandler } />
..
</div>
)
}
En el clickhandler ()
this.setState({ redirectTo: ''/path/some/where'' });
Espero eso ayude. Házmelo saber.
La forma más fácil de hacerlo:
this.props.history.push("/new/url")
Nota:
-
Es posible que desee pasar el
prop
history
del componente principal al componente en el que desea invocar la acción si no está disponible.
Luché con esto por un tiempo, algo tan simple, pero tan complicado, porque ReactJS es una forma completamente diferente de escribir aplicaciones web, ¡es muy extraño para nosotros, las personas mayores!
Creé un componente separado para abstraer el desastre:
// LinkButton.js
import React from "react";
import PropTypes from "prop-types";
import {Route} from ''react-router-dom'';
export default class LinkButton extends React.Component {
render() {
return (
<Route render={({history}) => (
<button {...this.props}
onClick={() => {
history.push(this.props.to)
}}>
{this.props.children}
</button>
)}/>
);
}
}
LinkButton.propTypes = {
to: PropTypes.string.isRequired
};
Luego agréguelo a su método
render()
:
<LinkButton className="btn btn-primary" to="/location">
Button Text
</LinkButton>
Mi respuesta es similar a la de Alex''s . No estoy seguro de por qué React-Router hizo esto tan innecesariamente complicado. ¿Por qué debería tener que envolver mi componente con un HoC solo para obtener acceso a lo que es esencialmente global?
De todos modos, si
<BrowserRouter>
un vistazo a cómo implementaron
<BrowserRouter>
, es solo un pequeño contenedor alrededor de la
history
.
Podemos extraer ese historial para poder importarlo desde cualquier lugar.
El truco, sin embargo, es que si está haciendo renderizado del lado del servidor e intenta
import
el módulo de historial, no funcionará porque usa API solo para el navegador.
Pero eso está bien porque generalmente solo redirigimos en respuesta a un clic o algún otro evento del lado del cliente.
Por lo tanto, probablemente esté bien simularlo:
// history.js
if(__SERVER__) {
module.exports = {};
} else {
module.exports = require(''history'').createBrowserHistory();
}
Con la ayuda de webpack, podemos definir algunos variables para saber en qué entorno estamos:
plugins: [
new DefinePlugin({
''__SERVER__'': ''false'',
''__BROWSER__'': ''true'', // you really only need one of these, but I like to have both
}),
Y ahora puedes
import history from ''./history'';
De donde sea. Simplemente devolverá un módulo vacío en el servidor.
Si no desea usar estos vars mágicos, solo tendrá que
require
en el objeto global donde sea necesario (dentro de su controlador de eventos).
import
no funcionará porque solo funciona en el nivel superior.
Para aquellos de ustedes que necesitan redirigir antes de iniciar completamente un enrutador usando
React Router
o
React Router Dom
. Puede proporcionar una redirección simplemente accediendo al objeto de historial y presionando un nuevo estado dentro de su constructor de
app.js
Considera lo siguiente:
function getSubdomain(hostname) {
let regexParse = new RegExp(''[a-z/-0-9]{2,63}/.[a-z/.]{2,5}$'');
let urlParts = regexParse.exec(hostname);
return hostname.replace(urlParts[0], '''').slice(0, -1);
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
hostState: true
};
if (getSubdomain(window.location.hostname).length > 0) {
this.state.hostState = false;
window.history.pushState('''', '''', ''./login'');
} else {
console.log(getSubdomain(window.location.hostname));
}
}
render() {
return (
<BrowserRouter>
{this.state.hostState ? (
<div>
<Route path="/login" component={LoginContainer}/>
<Route path="/" component={PublicContainer}/>
</div>
) : (
<div>
<Route path="/login" component={LoginContainer}/>
</div>
)
}
</BrowserRouter>)
}
}
Aquí queremos cambiar las Rutas de salida que dependen de un subdominio, al interactuar con el objeto de historial antes de que el componente se procese, podemos redirigir efectivamente sin dejar nuestras rutas intactas.
window.history.pushState('''', '''', ''./login'');
Paso 1: solo hay una cosa para importar:
import {Route} from ''react-router-dom'';
Paso 2: en tu ruta, pasa el historial:
<Route exact path=''/posts/add'' render={({history}) => (
<PostAdd
history={history}
/>
.)}/>
Paso 3: el historial se acepta como parte de los accesorios en el siguiente Componente, por lo que puede simplemente:
this.props.history.push(''/'');
Eso fue fácil y realmente poderoso.
Si se dirige a entornos de navegador, debe usar el paquete
react-router-dom
, en lugar de
react-router
.
Están siguiendo el mismo enfoque que React, para separar el núcleo, (
react
) y el código específico de la plataforma, (
react-dom
,
react-native
) con la sutil diferencia de que no necesita instalar dos paquetes separados , por lo que los paquetes de entorno contienen todo lo que necesita.
Puedes agregarlo a tu proyecto como:
yarn add react-router-dom
o
npm i react-router-dom
Lo primero que debe hacer es proporcionar un
<BrowserRouter>
como el principal componente principal en su aplicación.
<BrowserRouter>
usa la API de
history
HTML5 y la administra por usted, por lo que no tiene que preocuparse de crear una instancia usted mismo y pasarlo al componente
<BrowserRouter>
como accesorio (como lo necesitaba hacer en versiones anteriores).
En V4, para navegar programáticamente, necesita acceder al objeto de
history
, que está disponible a través del
context
Reaccionar, siempre que tenga un componente de
proveedor
<BrowserRouter>
como el principal padre en su aplicación.
La biblioteca expone a través del contexto el objeto
router
, que en sí mismo contiene el
history
como una propiedad.
La interfaz de
history
ofrece varios métodos de navegación, como
push
,
replace
y
goBack
, entre otros.
Puede consultar la lista completa de propiedades y métodos
here
.
Nota importante para los usuarios de Redux / Mobx
Si está utilizando redux o mobx como su biblioteca de administración de estado en su aplicación, es posible que haya encontrado problemas con los componentes que deberían tener en cuenta la ubicación pero que no se vuelven a reproducir después de activar una actualización de URL
Eso sucede porque
react-router
pasa la
location
a los componentes utilizando el modelo de contexto.
Tanto connect como observador crean componentes cuyos métodos shouldComponentUpdate hacen una comparación superficial de sus accesorios actuales y sus próximos accesorios. Esos componentes solo se volverán a renderizar cuando haya cambiado al menos un accesorio. Esto significa que para garantizar que se actualicen cuando cambie la ubicación, deberán recibir un accesorio que cambie cuando cambie la ubicación.
Los 2 enfoques para resolver esto son:
-
Envuelva su componente
conectado
en una
<Route />
sin<Route />
. El objeto delocation
actual es uno de los accesorios que un<Route>
pasa al componente que representa -
Envuelva su componente
conectado
con el componente de orden superior
withRouter
, que de hecho tiene el mismo efecto e inyecta lalocation
que un accesorio
Dejando eso de lado, hay cuatro formas de navegar programáticamente, ordenadas por recomendación:
1.- Usando un componente
<Route>
Promueve un estilo declarativo.
Antes de v4, los componentes
<Route />
se colocaban en la parte superior de la jerarquía de componentes, teniendo que pensar de antemano en la estructura de sus rutas.
Sin embargo, ahora puede tener componentes
<Route>
en
cualquier parte
de su árbol, lo que le permite tener un control más preciso para la representación condicional dependiendo de la URL.
Route
inyecta
match
,
location
e
history
como accesorios en su componente.
Los métodos de navegación (como
push
,
replace
,
goBack
...) están disponibles como propiedades del objeto de
history
.
Hay 3 formas de renderizar algo con una
Route
, utilizando
component
,
render
o accesorios secundarios, pero no use más de uno en la misma
Route
.
La elección depende del caso de uso, pero básicamente las dos primeras opciones solo mostrarán su componente si la
path
coincide con la ubicación de la URL, mientras que con los
children
el componente se mostrará si la ruta coincide con la ubicación o no (útil para ajustar la IU basada en coincidencia de URL).
Si desea personalizar la salida de representación de su componente
, debe envolver su componente en una función y usar la opción de
render
, para pasar a su componente cualquier otro accesorio que desee, además de la
match
, la
location
y el
history
.
Un ejemplo para ilustrar:
import { BrowserRouter as Router } from ''react-router-dom''
const ButtonToNavigate = ({ title, history }) => (
<button
type="button"
onClick={() => history.push(''/my-new-location'')}
>
{title}
</button>
);
const SomeComponent = () => (
<Route path="/" render={(props) => <ButtonToNavigate {...props} title="Navigate elsewhere" />} />
)
const App = () => (
<Router>
<SomeComponent /> // Notice how in v4 we can have any other component interleaved
<AnotherComponent />
</Router>
);
2.- Uso con
withRouter
HoC
Este componente de orden superior inyectará los mismos accesorios que
Route
.
Sin embargo, conlleva la limitación de que solo puede tener 1 HoC por archivo.
import { withRouter } from ''react-router-dom''
const ButtonToNavigate = ({ history }) => (
<button
type="button"
onClick={() => history.push(''/my-new-location'')}
>
Navigate
</button>
);
ButtonToNavigate.propTypes = {
history: React.PropTypes.shape({
push: React.PropTypes.func.isRequired,
}),
};
export default withRouter(ButtonToNavigate);
3.- Usando un componente
Redirect
Al representar un
<Redirect>
se navegará a una nueva ubicación.
Pero tenga en cuenta que,
de forma predeterminada
, la ubicación actual se reemplaza por la nueva, como las redirecciones del lado del servidor (HTTP 3xx).
La nueva ubicación es proporcionada por prop, que puede ser una cadena (URL para redirigir a) o un objeto de
location
.
Si desea
insertar una nueva entrada en el historial
, pase también un accesorio de
push
configúrelo como
true
<Redirect to="/your-new-location" push />
4.- Acceso al
router
manualmente a través del contexto
Un poco desanimado porque el
context
sigue siendo una API experimental y es probable que se rompa / cambie en futuras versiones de React
const ButtonToNavigate = (props, context) => (
<button
type="button"
onClick={() => context.router.history.push(''/my-new-location'')}
>
Navigate to a new location
</button>
);
ButtonToNavigate.contextTypes = {
router: React.PropTypes.shape({
history: React.PropTypes.object.isRequired,
}),
};
No hace falta decir que también hay otros componentes del enrutador destinados a ecosistemas que no son del navegador, como
<NativeRouter>
que replica una pila de navegación
en la memoria
y apunta a la plataforma React Native, disponible a través
react-router-native
paquete
react-router-native
.
Para cualquier referencia adicional, no dude en consultar los documentos oficiales . También hay un video hecho por uno de los coautores de la biblioteca que proporciona una introducción bastante interesante al react-router v4, destacando algunos de los principales cambios.
TL; DR:
if (navigate) {
return <Redirect to="/" push={true} />
}
La respuesta simple y declarativa es que debe usar
<Redirect to={URL} push={boolean} />
en combinación con
setState()
push: boolean: cuando es verdadero, la redirección empujará una nueva entrada al historial en lugar de reemplazar la actual.
import { Redirect } from ''react-router''
class FooBar extends React.Component {
state = {
navigate: false
}
render() {
const { navigate } = this.state
// here is the important part
if (navigate) {
return <Redirect to="/" push={true} />
}
// ^^^^^^^^^^^^^^^^^^^^^^^
return (
<div>
<button onClick={() => this.setState({ navigate: true })}>
Home
</button>
</div>
)
}
}
Ejemplo completo here . Lee más here .
PD. El ejemplo utiliza los inicializadores de propiedades ES7 + para inicializar el estado. Mira here también, si estás interesado.
También puede simplemente usar accesorios:
this.props.history.push(''new_url'')
Tuve un problema similar al migrar a React-Router v4, así que intentaré explicar mi solución a continuación.
No considere esta respuesta como la forma correcta de resolver el problema, imagino que hay una buena posibilidad de que surja algo mejor a medida que React Router v4 se vuelva más maduro y deje beta (incluso puede existir y simplemente no lo descubrí) .
Por contexto, tuve este problema porque ocasionalmente uso
Redux-Saga
para cambiar programáticamente el objeto de historial (por ejemplo, cuando un usuario se autentica con éxito).
En los documentos de React Router, eche un vistazo al
component
<Router>
y verá que tiene la capacidad de pasar su propio objeto de historia a través de un accesorio.
Esta es la esencia de la solución:
suministramos el objeto de historia
a
React-Router
desde un módulo
global
.
Pasos:
-
Instale el módulo de historial npm -
yarn add history
onpm install history --save
-
crear un archivo llamado
history.js
en su carpeta de nivelApp.js
(esta era mi preferencia)// src/history.js import createHistory from ''history/createBrowserHistory''; export default createHistory();`
-
Agregue este objeto de historial a su componente de enrutador de esta manera
// src/App.js import history from ''../your/path/to/history.js;'' <Router history={history}> // Route tags here </Router>
-
Ajuste la URL como antes importando su objeto de historial global:
import history from ''../your/path/to/history.js;'' history.push(''new/path/here/'');
Todo debería estar sincronizado ahora, y también tiene acceso a una forma de configurar el objeto de historial mediante programación y no a través de un componente / contenedor.