¿Cómo pasar a la historia en React Router v4?

361

En la versión actual de React Router (v3) puedo aceptar una respuesta del servidor y usarla browserHistory.pushpara ir a la página de respuesta apropiada. Sin embargo, esto no está disponible en v4, y no estoy seguro de cuál es la forma adecuada de manejar esto.

En este ejemplo, usando Redux, components / app-product-form.js llama this.props.addProduct(props)cuando un usuario envía el formulario. Cuando el servidor devuelve un éxito, el usuario es llevado a la página Carrito.

// actions/index.js
export function addProduct(props) {
  return dispatch =>
    axios.post(`${ROOT_URL}/cart`, props, config)
      .then(response => {
        dispatch({ type: types.AUTH_USER });
        localStorage.setItem('token', response.data.token);
        browserHistory.push('/cart'); // no longer in React Router V4
      });
}

¿Cómo puedo hacer una redirección a la página del carrito desde la función React Router v4?

Chris
fuente
Solo para agregar a esto desde la última solución ofrecida y desde sugerencias en los problemas de React Router en GitHub, usar el contextpara pasar lo que necesita manualmente es un "no ir". A menos que sea un autor de biblioteca, no debería ser necesario usarlo. De hecho, Facebook recomienda no hacerlo.
Chris
@ Chris ¿Encontraste una solución para esto? Necesito empujar a un componente diferente en acción, igual que usted ha explicado aquí
Sr.G
@ Mr.G, lamentablemente no lo he hecho. Lo último que leí fue el equipo de React Training que mantiene que React Router tiene un paquete redux disponible. No he tenido suerte de hacerlo funcionar, y no han trabajado mucho para resolverlo.
Chris
¿Por qué no podemos usar windows.location.href = URL? ¿Hay algo malo en usarlo para cambiar la URL y redirigir?
Shan
No veo por qué no, pero la opción de usar historytambién funciona para React Native como una opción, además de una opción adicional de admitir navegadores heredados.
Chris

Respuestas:

308

Puede usar los historymétodos fuera de sus componentes. Intenta de la siguiente manera.

Primero, cree un historyobjeto que use el paquete de historial :

// src/history.js

import { createBrowserHistory } from 'history';

export default createBrowserHistory();

Luego envuélvalo <Router>( tenga en cuenta que debe usarlo en import { Router }lugar de import { BrowserRouter as Router }):

// src/index.jsx

// ...
import { Router, Route, Link } from 'react-router-dom';
import history from './history';

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      <div>
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/login">Login</Link></li>
        </ul>
        <Route exact path="/" component={HomePage} />
        <Route path="/login" component={LoginPage} />
      </div>
    </Router>
  </Provider>,
  document.getElementById('root'),
);

Cambie su ubicación actual desde cualquier lugar, por ejemplo:

// src/actions/userActionCreators.js

// ...
import history from '../history';

export function login(credentials) {
  return function (dispatch) {
    return loginRemotely(credentials)
      .then((response) => {
        // ...
        history.push('/');
      });
  };
}

UPD : También puede ver un ejemplo ligeramente diferente en las Preguntas frecuentes de React Router .

Oleg Belostotsky
fuente
24
He intentado hacer exactamente lo que dijo @OlegBelostotsky, pero después history.push('some path'), la URL cambia pero la página no cambia. Tengo que ponerlo window.location.reload()después en algunas partes de mi código solo para que funcione. Sin embargo, en un caso, tengo que mantener el árbol de estado redux, y la recarga lo destruye. ¿Alguna otra solución?
sdabrutas
2
@idunno Intente usar el withRoutercomponente de orden superior.
Oleg Belostotsky
Esto me arrojó un error que indica: createBrowserHistory no es una función. ¿Que puedo hacer?
AKJ
@AKJ Quizás import createHistory from 'history/createBrowserHistory'; export default createHistory();sea ​​trabajo.
Oleg Belostotsky
1
Perdón por el voto negativo :). Si bien esto también debería funcionar, la forma correcta de manejar esto es cómo Chris respondió: stackoverflow.com/a/42716055/491075 .
gion_13
340

React Router v4 es fundamentalmente diferente de v3 (y anterior) y no puede hacer lo browserHistory.push()que solía hacer.

Esta discusión parece relacionada si quieres más información:

  • Crear uno nuevo browserHistoryno funcionará porque<BrowserRouter> crea su propia instancia de historial y escucha los cambios en eso. Entonces, una instancia diferente cambiará la URL pero no actualizará la <BrowserRouter>.
  • browserHistory no está expuesto por react-router en v4, solo en v2.

En cambio, tiene algunas opciones para hacer esto:

  • Utilizar el withRouter componente de alto orden

    En su lugar, debe usar el withRoutercomponente de orden superior y ajustarlo al componente que empujará al historial. Por ejemplo:

    import React from "react";
    import { withRouter } from "react-router-dom";
    
    class MyComponent extends React.Component {
      ...
      myFunction() {
        this.props.history.push("/some/Path");
      }
      ...
    }
    export default withRouter(MyComponent);

    Consulte la documentación oficial para más información:

    Usted puede obtener acceso a la historypropiedades del objeto y el más cercano <Route>que está matcha través del componente de orden superior withRouter. withRouter será volver a hacer su componente cada vez que cambia la ruta con los mismos apoyos que <Route>rendir puntales: { match, location, history }.


  • Utilizar el context API

    El uso del contexto podría ser una de las soluciones más fáciles, pero al ser una API experimental, es inestable y no es compatible. Úselo solo cuando todo lo demás falla. Aquí hay un ejemplo:

    import React from "react";
    import PropTypes from "prop-types";
    
    class MyComponent extends React.Component {
      static contextTypes = {
        router: PropTypes.object
      }
      constructor(props, context) {
         super(props, context);
      }
      ...
      myFunction() {
        this.context.router.history.push("/some/Path");
      }
      ...
    }

    Echa un vistazo a la documentación oficial sobre contexto:

    Si desea que su aplicación sea estable, no use el contexto. Es una API experimental y es probable que se rompa en futuras versiones de React.

    Si insiste en usar el contexto a pesar de estas advertencias, intente aislar su uso del contexto en un área pequeña y evite usar la API de contexto directamente cuando sea posible para que sea más fácil actualizar cuando la API cambie.

Chris
fuente
99
Sí, lo intenté. Gracias por preguntar. :-) Entonces, ¿cómo se obtiene el contexto en esta función de acción? Hasta ahora, aparece como indefinido.
Chris
1
Llevo unos días investigando este tema y no he podido hacer que funcione. Incluso usando el ejemplo anterior, sigo obteniendo el enrutador no está definido en el contexto. Actualmente estoy usando react v15.5.10, react-router-dom v4.1.1, prop-types 15.5.10. La documentación sobre esto es escasa y poco clara.
Stu
55
@Stu esto debería funcionarthis.context.router.history.push('/path');
Tushar Khatiwada
1
@Chris De hecho, si intenta crear una instancia del objeto de historial y usarlo usted mismo, cambiará la URL pero no representará el componente, como mencionó anteriormente. Pero, ¿cómo usar "history.push" fuera del componente y forzar el componente también?
genial
52
Esto no responde a la pregunta que se hace sobre cómo acceder al historial. Empuje FUERA de un componente. Usar withRouter o el contexto no son opciones para cuando está fuera de un componente.
SunshinyDoyle
49

Ahora con react-router v5 puede usar el enlace de ciclo de vida useHistory de esta manera:

import { useHistory } from "react-router-dom";

function HomeButton() {
  let history = useHistory();

  function handleClick() {
    history.push("/home");
  }

  return (
    <button type="button" onClick={handleClick}>
      Go home
    </button>
  );
}

Lea más en: https://reacttraining.com/react-router/web/api/Hooks/usehistory

Hadi Abu
fuente
1
¡Esa es una actualización muy bienvenida! ¡Gracias!
Chris
¿Hay alguna forma específica de configurar esto? Estoy llamando a lo siguiente let history = useHistory();pero Object is not callableobtengo un error cuando intenté ver qué uso tiene la historia console.log(useHistory)como indefinido. utilizando"react-router-dom": "^5.0.1"
steff_bdh
@steff_bdh necesita actualizarlo en su archivo package.json a "react-router-dom": "^ 5.0.1" y ejecutar 'npm install'
Hadi Abu
2
Agradable, pero no puedo usar el gancho dentro de las clases de acción redux ya que no son React componente / función
Jay
¿Cómo usaría esto para la redirección al iniciar sesión usando (async)? Aquí está la pregunta => stackoverflow.com/questions/62154408/…
theairbend3r
29

La manera más simple en React Router 4 es usar

this.props.history.push('/new/url');

Pero para usar este método, su componente existente debe tener acceso al historyobjeto. Podemos acceder por

  1. Si su componente está vinculado Routedirectamente, entonces su componente ya tiene acceso ahistory objeto.

    p.ej:

    <Route path="/profile" component={ViewProfile}/>

    Aquí ViewProfiletiene acceso a history.

  2. Si no está conectado a Route directamente.

    p.ej:

    <Route path="/users" render={() => <ViewUsers/>}

    Entonces tenemos que usar withRouter una función de orden superior para deformar el componente existente.

    ViewUsersComponente interior

    • import { withRouter } from 'react-router-dom';

    • export default withRouter(ViewUsers);

    Eso es todo, su ViewUserscomponente tiene acceso ahistory objeto.

ACTUALIZAR

2- en este escenario, pase toda la ruta propsa su componente, y luego podremos accederthis.props.history desde el componente incluso sin unHOC

p.ej:

<Route path="/users" render={props => <ViewUsers {...props} />}
Saahithyan Vigneswaran
fuente
1
¡Excelente! Su segundo método también me sirvió, ya que mi componente (que necesita acceso this.props.history) proviene de un HOC, lo que significa que no está directamente relacionado con el Route, como usted explica.
cjauvin
25

Así es como lo hice:

import React, {Component} from 'react';

export default class Link extends Component {
    constructor(props) {
        super(props);
        this.onLogout = this.onLogout.bind(this);
    }
    onLogout() {
        this.props.history.push('/');
    }
    render() {
        return (
            <div>
                <h1>Your Links</h1>
                <button onClick={this.onLogout}>Logout</button>
            </div>
        );
    }
}

Utilizar this.props.history.push('/cart'); para redirigir a la página del carrito, se guardará en el objeto de historial.

Disfruta, Michael.

Michael Horojanski
fuente
2
Sí, parece que dentro de los componentes puedes empujar bien. La única forma de afectar la navegación fuera de un componente es con redireccionamiento.
Chris
14
Esto no responde a la pregunta que se hace sobre cómo acceder al historial. Empuje FUERA de un componente. Usar this.props.history no es una opción para cuando está fuera de un componente.
SunshinyDoyle
22

De acuerdo con la documentación de React Router v4 - sesión de Redux Deep Integration

Se necesita una integración profunda para:

"poder navegar enviando acciones"

Sin embargo, recomiendan este enfoque como una alternativa a la "integración profunda":

"En lugar de enviar acciones para navegar, puede pasar el objeto de historial proporcionado para enrutar componentes a sus acciones y navegar con él allí".

Entonces puede envolver su componente con el componente de orden superior withRouter:

export default withRouter(connect(null, { actionCreatorName })(ReactComponent));

que pasará la API de historial a los accesorios. Entonces puedes llamar al creador de la acción pasando la historia como un parámetro. Por ejemplo, dentro de su ReactComponent:

onClick={() => {
  this.props.actionCreatorName(
    this.props.history,
    otherParams
  );
}}

Luego, dentro de tus acciones / index.js:

export function actionCreatorName(history, param) {
  return dispatch => {
    dispatch({
      type: SOME_ACTION,
      payload: param.data
    });
    history.push("/path");
  };
}
alainbex
fuente
19

Pregunta desagradable, me llevó bastante tiempo, pero finalmente, lo resolví de esta manera:

Envuelva su contenedor con withRoutery pase el historial a su acción en mapDispatchToProps función. En acción, use history.push ('/ url') para navegar.

Acción:

export function saveData(history, data) {
  fetch.post('/save', data)
     .then((response) => {
       ...
       history.push('/url');
     })
};

Envase:

import { withRouter } from 'react-router-dom';
...
const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    save: (data) => dispatch(saveData(ownProps.history, data))}
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Container));

Esto es válido para reaccionar Router v4.x .

Khrystyna Skvarok
fuente
Gracias, su solución withRouter funciona con mecanografiado, pero es bastante lenta en comparación con import { createBrowserHistory } from 'history' alguna idea anterior , ¿por favor?
Jay
7

this.context.history.push no trabajará.

Me las arreglé para empujar trabajando así:

static contextTypes = {
    router: PropTypes.object
}

handleSubmit(e) {
    e.preventDefault();

    if (this.props.auth.success) {
        this.context.router.history.push("/some/Path")
    }

}
Aimar
fuente
99
Esto no responde a la pregunta que se hace sobre cómo acceder al historial. Empuje FUERA de un componente. Usar this.context no es una opción para cuando está fuera de un componente.
SunshinyDoyle
6

En este caso, estás pasando accesorios a tu puño. Entonces puedes simplemente llamar

props.history.push('/cart')

Si este no es el caso, aún puede pasar el historial de su componente

export function addProduct(data, history) {
  return dispatch => {
    axios.post('/url', data).then((response) => {
      dispatch({ type: types.AUTH_USER })
      history.push('/cart')
    })
  }
}
usuario3812377
fuente
6

Ofrezco una solución más en caso de que valga la pena para otra persona.

Tengo un history.jsarchivo donde tengo lo siguiente:

import createHistory from 'history/createBrowserHistory'
const history = createHistory()
history.pushLater = (...args) => setImmediate(() => history.push(...args))
export default history

Luego, en mi raíz donde defino mi enrutador, uso lo siguiente:

import history from '../history'
import { Provider } from 'react-redux'
import { Router, Route, Switch } from 'react-router-dom'

export default class Root extends React.Component {
  render() {
    return (
     <Provider store={store}>
      <Router history={history}>
       <Switch>
        ...
       </Switch>
      </Router>
     </Provider>
    )
   }
  }

Finalmente, en mi actions.jsimporto Historial y uso pushLater

import history from './history'
export const login = createAction(
...
history.pushLater({ pathname: PATH_REDIRECT_LOGIN })
...)

De esta manera, puedo impulsar nuevas acciones después de las llamadas a la API.

¡Espero eso ayude!

Diego
fuente
4

Si está usando Redux, entonces recomendaría usar el paquete npm react-router-redux . Le permite despachar acciones de navegación de la tienda Redux.

Debe crear la tienda como se describe en su archivo Léame .

El caso de uso más fácil:

import { push } from 'react-router-redux'

this.props.dispatch(push('/second page'));

Segundo caso de uso con contenedor / componente:

Envase:

import { connect } from 'react-redux';
import { push } from 'react-router-redux';

import Form from '../components/Form';

const mapDispatchToProps = dispatch => ({
  changeUrl: url => dispatch(push(url)),
});

export default connect(null, mapDispatchToProps)(Form);

Componente:

import React, { Component } from 'react';
import PropTypes from 'prop-types';

export default class Form extends Component {
  handleClick = () => {
    this.props.changeUrl('/secondPage');
  };

  render() {
    return (
      <div>
        <button onClick={this.handleClick}/>
      </div>Readme file
    );
  }
}
Negro
fuente
1
Esto no funciona con react-router-redux a menos que esté utilizando la nextversión, que todavía está en desarrollo en este momento.
froginvasion
4

Pude lograr esto usando bind(). Quería hacer clic en un botón index.jsx, publicar algunos datos en el servidor, evaluar la respuesta y redirigir a success.jsx. Así es como lo resolví ...

index.jsx:

import React, { Component } from "react"
import { postData } from "../../scripts/request"

class Main extends Component {
    constructor(props) {
        super(props)
        this.handleClick = this.handleClick.bind(this)
        this.postData = postData.bind(this)
    }

    handleClick() {
        const data = {
            "first_name": "Test",
            "last_name": "Guy",
            "email": "[email protected]"
        }

        this.postData("person", data)
    }

    render() {
        return (
            <div className="Main">
                <button onClick={this.handleClick}>Test Post</button>
            </div>
        )
    }
}

export default Main

request.js:

import { post } from "./fetch"

export const postData = function(url, data) {
    // post is a fetch() in another script...
    post(url, data)
        .then((result) => {
            if (result.status === "ok") {
                this.props.history.push("/success")
            }
        })
}

success.jsx:

import React from "react"

const Success = () => {
    return (
        <div className="Success">
            Hey cool, got it.
        </div>
    )
}

export default Success

Entonces, al vincularme thisa postDatain index.jsx, pude acceder this.props.historya request.js... luego puedo reutilizar esta función en diferentes componentes, solo tengo que asegurarme de recordar incluir this.postData = postData.bind(this)en el constructor().

skwidbreth
fuente
3

Aquí está mi truco (este es mi archivo de nivel raíz, con un poco de redux mezclado allí, aunque no estoy usando react-router-redux):

const store = configureStore()
const customHistory = createBrowserHistory({
  basename: config.urlBasename || ''
})

ReactDOM.render(
  <Provider store={store}>
    <Router history={customHistory}>
      <Route component={({history}) => {
        window.appHistory = history
        return (
          <App />
        )
      }}/>
    </Router>
  </Provider>,
  document.getElementById('root')
)

Luego puedo usarlo en window.appHistory.push()cualquier lugar que desee (por ejemplo, en mis funciones / thunks / sagas de la tienda redux, etc.) Esperaba poder usarlo, window.customHistory.push()pero por alguna razón react-routernunca pareció actualizarse a pesar de que la URL cambió. Pero de esta manera tengo los react-routerusos de instancia EXACT . No me encanta poner cosas en el ámbito global, y esta es una de las pocas cosas con las que haría eso. Pero es mejor que cualquier otra alternativa que haya visto en mi opinión.

Joao
fuente
3

Usar devolución de llamada. ¡Funcionó para mí!

export function addProduct(props, callback) {
  return dispatch =>
    axios.post(`${ROOT_URL}/cart`, props, config)
    .then(response => {
    dispatch({ type: types.AUTH_USER });
    localStorage.setItem('token', response.data.token);
    callback();
  });
}

En el componente, solo tiene que agregar la devolución de llamada

this.props.addProduct(props, () => this.props.history.push('/cart'))
Priyansh Rastogi
fuente
3

así que la forma en que lo hago es: - en lugar de redirigir usando history.push, solo uso el Redirectcomponente de react-router-dom Al usar este componente, puede pasar push=truey se encargará del resto

import * as React from 'react';
import { Redirect } from 'react-router-dom';
class Example extends React.Component {
  componentDidMount() {
    this.setState({
      redirectTo: '/test/path'
    });
  }

  render() {
    const { redirectTo } = this.state;

    return <Redirect to={{pathname: redirectTo}} push={true}/>
  }
}
LuizAsFight
fuente
este es el correcto que no rompe el ciclo de
reacción
1

React router V4 ahora permite utilizar el accesorio de historial de la siguiente manera:

this.props.history.push("/dummy",value)

Se puede acceder al valor siempre que el accesorio de ubicación esté disponible como state:{value}estado no componente.

Snivio
fuente
1
Esto no responde a la pregunta que se hace sobre cómo acceder al historial. Empuje FUERA de un componente. Usar this.props.history no es una opción para cuando está fuera de un componente.
Arkady
0

puedes usarlo así como lo hago para iniciar sesión y muchas cosas diferentes

class Login extends Component {
  constructor(props){
    super(props);
    this.login=this.login.bind(this)
  }


  login(){
this.props.history.push('/dashboard');
  }


render() {

    return (

   <div>
    <button onClick={this.login}>login</login>
    </div>

)
jadlmir
fuente
0
/*Step 1*/
myFunction(){  this.props.history.push("/home"); }
/**/
 <button onClick={()=>this.myFunction()} className={'btn btn-primary'}>Go 
 Home</button>
David Bagdasaryan
fuente
¡No hay necesidad de importar!
David Bagdasaryan
1
Si bien este código puede responder la pregunta, proporcionar un contexto adicional con respecto a por qué y / o cómo responde la pregunta mejora su valor a largo plazo.
rollstuhlfahrer
0

el primer paso envuelve tu aplicación en el enrutador

import { BrowserRouter as Router } from "react-router-dom";
ReactDOM.render(<Router><App /></Router>, document.getElementById('root'));

Ahora toda mi aplicación tendrá acceso a BrowserRouter. Paso dos importo Ruta y luego paso esos accesorios. Probablemente en uno de tus archivos principales.

import { Route } from "react-router-dom";

//lots of code here

//somewhere in my render function

    <Route
      exact
      path="/" //put what your file path is here
      render={props => (
      <div>
        <NameOfComponent
          {...props} //this will pass down your match, history, location objects
        />
      </div>
      )}
    />

Ahora, si ejecuto console.log (this.props) en mi archivo js de componente, debería obtener algo similar a esto

{match: {…}, location: {…}, history: {…}, //other stuff }

Paso 2 Puedo acceder al objeto de historial para cambiar mi ubicación

//lots of code here relating to my whatever request I just ran delete, put so on

this.props.history.push("/") // then put in whatever url you want to go to

Además, solo soy un estudiante de bootcamp de codificación, así que no soy un experto, pero sé que también puedes usar

window.location = "/" //wherever you want to go

Corrígeme si me equivoco, pero cuando lo probé, volvió a cargar toda la página, lo que pensé que derrotó el punto de usar React.


fuente
0

Crea una costumbre Routercon la suya browserHistory:

import React from 'react';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';

export const history = createBrowserHistory();

const ExtBrowserRouter = ({children}) => (
  <Router history={history} >
  { children }
  </Router>
);

export default ExtBrowserRouter

A continuación, en su raíz donde define su Router, use lo siguiente:

import React from 'react';       
import { /*BrowserRouter,*/ Route, Switch, Redirect } from 'react-router-dom';

//Use 'ExtBrowserRouter' instead of 'BrowserRouter'
import ExtBrowserRouter from './ExtBrowserRouter'; 
...

export default class Root extends React.Component {
  render() {
    return (
      <Provider store={store}>
        <ExtBrowserRouter>
          <Switch>
            ...
            <Route path="/login" component={Login}  />
            ...
          </Switch>
        </ExtBrowserRouter>
      </Provider>
    )
  }
}

Finalmente, importe historydonde lo necesite y úselo:

import { history } from '../routers/ExtBrowserRouter';
...

export function logout(){
  clearTokens();      
  history.push('/login'); //WORKS AS EXPECTED!
  return Promise.reject('Refresh token has expired');
}
Nolesh
fuente
0

Si desea usar el historial al pasar una función como valor al accesorio de un Componente, con react-router 4 puede simplemente desestructurar el historyaccesorio en el atributo de renderizado del <Route/>Componente y luego usarhistory.push()

    <Route path='/create' render={({history}) => (
      <YourComponent
        YourProp={() => {
          this.YourClassMethod()
          history.push('/')
        }}>
      </YourComponent>
    )} />

Nota: Para que esto funcione, debe envolver el componente BrowserRouter de React Router alrededor de su componente raíz (por ejemplo, que podría estar en index.js)

Kewal Shah
fuente