Come passare alla cronologia in React Router v4?


361

Nella versione corrente di React Router (v3) posso accettare una risposta del server e utilizzare browserHistory.pushper andare alla pagina di risposta appropriata. Tuttavia, questo non è disponibile in v4 e non sono sicuro di quale sia il modo appropriato di gestirlo.

In questo esempio, utilizzando Redux, Components / app-product-form.js chiama this.props.addProduct(props)quando un utente invia il modulo. Quando il server restituisce un esito positivo, l'utente viene portato alla pagina Carrello.

// 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
      });
}

Come posso effettuare un reindirizzamento alla pagina Carrello dalla funzione per React Router v4?


Solo per aggiungere a questo dall'ultima soluzione offerta e dai suggerimenti nei problemi di React Router su GitHub, usare il contextper passare ciò che è necessario manualmente è un "no go". A meno che non sia un autore di biblioteche, non dovrebbe esserci la necessità di usarlo. In effetti, Facebook lo sconsiglia.
Chris

@Chris hai trovato una soluzione per questo? ho bisogno di spingere verso un altro componente in azione, come hai spiegato qui
Mr.G

@ Mr.G, purtroppo non l'ho fatto. L'ultima volta che ho letto è stato il team di React Training che sostiene che React Router ha un pacchetto redux disponibile. Non ho avuto fortuna a farlo funzionare e non hanno lavorato molto per risolverlo.
Chris,

Perché non possiamo usare windows.location.href = URL? C'è qualcosa di sbagliato nell'usarlo per modificare l'URL e il reindirizzamento?
Shan

Non vedo perché no, ma l'opzione per l'utilizzo historyfunziona anche con React Native come opzione e un'opzione aggiuntiva di supporto dei browser legacy.
Chris,

Risposte:


308

È possibile utilizzare i historymetodi al di fuori dei componenti. Prova nel modo seguente.

Innanzitutto, crea un historyoggetto usando il pacchetto di cronologia :

// src/history.js

import { createBrowserHistory } from 'history';

export default createBrowserHistory();

Quindi avvolgilo <Router>( tieni presente che dovresti usare import { Router }invece di 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'),
);

Modifica la posizione corrente da qualsiasi luogo, ad esempio:

// src/actions/userActionCreators.js

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

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

UPD : puoi anche vedere un esempio leggermente diverso nelle FAQ di React Router .


24
Ho provato a fare esattamente quello che ha detto @OlegBelostotsky, ma dopo history.push('some path')l'URL cambia ma la pagina non cambia. Devo inserirlo window.location.reload()dopo alcune parti del mio codice solo per farlo funzionare. Tuttavia, in un caso, devo mantenere l'albero di stato redux e ricaricarlo lo distrugge. Qualche altra soluzione?
sdabrutas,

2
@idunno Prova a utilizzare il withRoutercomponente di ordine superiore.
Oleg Belostotsky,

Questo mi ha gettato un errore affermando: createBrowserHistory non è una funzione. Cosa posso fare?
AKJ

@AKJ Forse import createHistory from 'history/createBrowserHistory'; export default createHistory();funzionerà.
Oleg Belostotsky,

1
Ci scusiamo per il downvote :). Anche se questo dovrebbe funzionare, il modo corretto di gestirlo è la risposta di Chris: stackoverflow.com/a/42716055/491075 .
gion_13,

340

React Router v4 è fondamentalmente diverso dalla v3 (e precedenti) e non si può fare browserHistory.push()come una volta.

Questa discussione sembra correlata se vuoi maggiori informazioni:

  • La creazione di un nuovo browserHistorynon funzionerà perché <BrowserRouter>crea la propria istanza della cronologia e ascolta le modifiche. Quindi un'istanza diversa cambierà l'URL ma non aggiornerà <BrowserRouter>.
  • browserHistory non è esposto dal reagente-router in v4, solo in v2.

Invece hai alcune opzioni per farlo:

  • Utilizzare il withRoutercomponente di ordine superiore

    Dovresti invece utilizzare il withRoutercomponente di ordine superiore e inserirlo nel componente che passerà alla cronologia. Per esempio:

    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);

    Consulta la documentazione ufficiale per maggiori informazioni:

    È possibile ottenere l'accesso alla historyproprietà dell'oggetto e la più vicina <Route>'s matchtramite il componente di ordine superiore withRouter. withRouter si ri-renderizzare la sua componente ogni volta il percorso cambia con le stesse oggetti di scena come <Route>il rendering oggetti di scena: { match, location, history }.


  • Usa l' contextAPI

    L'uso del contesto potrebbe essere una delle soluzioni più semplici, ma essendo un'API sperimentale è instabile e non supportato. Usalo solo quando tutto il resto fallisce. Ecco un esempio:

    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");
      }
      ...
    }

    Dai un'occhiata alla documentazione ufficiale sul contesto:

    Se vuoi che l'applicazione sia stabile, non usare il contesto. È un'API sperimentale ed è probabile che rompa nelle versioni future di React.

    Se insisti nell'utilizzare il contesto nonostante questi avvisi, prova a isolare il tuo uso del contesto in una piccola area ed evita di utilizzare l'API di contesto direttamente quando possibile, in modo che sia più facile aggiornarlo quando l'API cambia.


9
Sì, l'ho provato. Grazie per avermelo chiesto. :-) Quindi come si inserisce il contesto in questa funzione di azione? Finora, sta arrivando come indefinito.
Chris

1
Ho studiato questo argomento per alcuni giorni e non sono stato in grado di farlo funzionare. Anche usando l'esempio sopra continuo a ottenere router non è definito nel contesto. Attualmente sto usando la reazione v15.5.10, reazioni-router-dom v4.1.1, i tipi di prop 15.5.10. La documentazione intorno a questo è scarsa e poco chiara.
Stu,

5
@Stu dovrebbe funzionarethis.context.router.history.push('/path');
Tushar Khatiwada,

1
@Chris In effetti, se provi a creare un'istanza dell'oggetto della cronologia e lo usi da solo, cambierà l'URL ma non renderà il componente - come hai detto sopra. Ma come usare "history.push" al di fuori del componente e forzare anche il rendering del componente?
fico

52
Questo non risponde alla domanda che si pone quale è come accedere a history.push ESTERNO di un componente. L'uso di withRouter o il contesto non sono opzioni per l'esterno di un componente.
SunshinyDoyle,

49

Ora con reagente-router v5 è possibile utilizzare l'hook del ciclo di vita useHistory in questo modo:

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>
  );
}

leggi di più su: https://reacttraining.com/react-router/web/api/Hooks/usehistory


1
È un aggiornamento molto gradito! Grazie!
Chris,

C'è un modo specifico di cui ho bisogno per configurarlo, sto chiamando il seguente let history = useHistory();ma sto ricevendo un Object is not callableerrore, quando ho provato a vedere che usoHistory viene console.log(useHistory)visualizzato come non definito. utilizzando"react-router-dom": "^5.0.1"
steff_bdh,

@steff_bdh è necessario aggiornarlo nel file package.json su "reagire-router-dom": "^ 5.0.1" ed eseguire 'npm install'
Hadi Abu

2
Bello, ma non posso usare l'hook all'interno delle classi di azione di redux in quanto non sono componenti / funzioni di React
Jay,

Come lo useresti per il reindirizzamento al login usando (asincrono). Ecco la domanda => stackoverflow.com/questions/62154408/...
theairbend3r

29

Il modo più semplice in React Router 4 è usare

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

Ma per utilizzare questo metodo, il componente esistente dovrebbe avere accesso historyall'oggetto. Siamo in grado di accedere da

  1. Se il componente è collegato Routedirettamente, allora il componente ha già accesso historyall'oggetto.

    per esempio:

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

    Qui ViewProfileha accesso a history.

  2. Se non collegato Routedirettamente.

    per esempio:

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

    Quindi dobbiamo usare withRouterun comando di ordine superiore per deformare il componente esistente.

    ViewUsersComponente interno

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

    • export default withRouter(ViewUsers);

    Ora è tutto, il tuo ViewUserscomponente ha accesso historyall'oggetto.

AGGIORNARE

2- in questo scenario, passa tutto il percorso propsal componente e quindi possiamo accedere this.props.historydal componente anche senza aHOC

per esempio:

<Route path="/users" render={props => <ViewUsers {...props} />}

1
Eccellente! Il tuo secondo metodo ha funzionato anche per me, poiché il mio componente (che deve accedere a this.props.history) proviene da un HOC, il che significa che non è direttamente collegato a Route, come spieghi.
cjauvin,

25

Ecco come l'ho fatto:

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>
        );
    }
}

Utilizzare this.props.history.push('/cart');per reindirizzare alla pagina del carrello verrà salvata nell'oggetto cronologia.

Divertiti, Michael.


2
Sì, sembra che all'interno dei componenti si possa spingere bene. L'unico modo per influire sulla navigazione all'esterno di un componente è con il reindirizzamento.
Chris,

14
Questo non risponde alla domanda che si pone quale è come accedere a history.push ESTERNO di un componente. L'uso di this.props.history non è un'opzione per l'esterno di un componente.
SunshinyDoyle,

22

Secondo la documentazione di React Router v4 - sessione Redux Deep Integration

È necessaria una profonda integrazione per:

"essere in grado di navigare inviando azioni"

Tuttavia, raccomandano questo approccio come alternativa alla "profonda integrazione":

"Invece di inviare azioni per navigare puoi passare l'oggetto storico fornito per instradare i componenti alle tue azioni e navigare con esso lì."

Quindi puoi avvolgere il tuo componente con il componente di ordine superiore withRouter:

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

che passerà l'API della cronologia agli oggetti di scena. Quindi puoi chiamare il creatore dell'azione che passa la cronologia come parametro. Ad esempio, all'interno di ReactComponent:

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

Quindi, all'interno di actions / index.js:

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

19

Brutta domanda, mi ci è voluto parecchio tempo, ma alla fine l'ho risolto in questo modo:

Avvolgi il tuo contenitore withRoutere passa la cronologia all'azione in mapDispatchToPropsfunzione. In azione usa history.push ('/ url') per navigare.

Azione:

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

Contenitore:

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));

Questo è valido per Reagire Router v4.x .


Grazie, la tua soluzione withRouter funziona con dattiloscritto ma è piuttosto lenta rispetto a prima import { createBrowserHistory } from 'history' qualche idea per favore?
Jay

7

this.context.history.push non funzionerà.

Sono riuscito a far funzionare la spinta in questo modo:

static contextTypes = {
    router: PropTypes.object
}

handleSubmit(e) {
    e.preventDefault();

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

}

9
Questo non risponde alla domanda che si pone quale è come accedere a history.push ESTERNO di un componente. L'uso di this.context non è un'opzione per l'esterno di un componente.
SunshinyDoyle,

6

In questo caso stai passando oggetti di scena al tuo thunk. Quindi puoi semplicemente chiamare

props.history.push('/cart')

In caso contrario, puoi comunque passare la cronologia dal tuo componente

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

6

Offro un'altra soluzione nel caso in cui sia utile per qualcun altro.

Ho un history.jsfile in cui ho il seguente:

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

Successivamente, sul mio Root dove definisco il mio router, utilizzo quanto segue:

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>
    )
   }
  }

Infine, sul mio actions.jsimportare la cronologia e utilizzo pushLater

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

In questo modo, posso passare a nuove azioni dopo le chiamate API.

Spero che sia d'aiuto!


4

Se stai usando Redux, allora consiglierei di usare il pacchetto npm reply -router-redux . Ti consente di inviare azioni di navigazione del negozio Redux.

Devi creare un negozio come descritto nel loro file Leggimi .

Il caso d'uso più semplice:

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

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

Secondo caso d'uso con contenitore / componente:

Contenitore:

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
    );
  }
}

1
Questo non funziona con reagire-router-redux a meno che non si stia utilizzando la nextversione, che è ancora in fase di sviluppo in questo momento!
froginvasion,

4

Sono stato in grado di ottenere questo risultato utilizzando bind(). Volevo fare clic su un pulsante index.jsx, pubblicare alcuni dati sul server, valutare la risposta e reindirizzare a success.jsx. Ecco come l'ho risolto ...

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": "test@test.com"
        }

        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

Quindi legando thisa postDatain index.jsx, sono stato in grado di accedere this.props.historya request.js... quindi posso riutilizzare questa funzione in diversi componenti, devo solo assicurarmi di ricordarmi di includere this.postData = postData.bind(this)in constructor().


3

Ecco il mio hack (questo è il mio file a livello di root, con un po 'di redux mischiato lì dentro, anche se non lo sto 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')
)

Posso quindi usare window.appHistory.push()dove voglio (ad esempio, nelle funzioni / thunks / sagas del mio negozio Redux) che speravo di poter usare, window.customHistory.push()ma per qualche motivo react-routernon mi è mai sembrato di aggiornarlo nonostante l'URL fosse cambiato. Ma in questo modo ho react-routerusato l' istanza EXACT . Non mi piace mettere le cose nell'ambito globale, e questa è una delle poche cose con cui lo farei. Ma è meglio di qualsiasi altra alternativa che ho visto IMO.


3

Usa callback. Ha funzionato per me!

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

Nel componente, devi solo aggiungere il callback

this.props.addProduct(props, () => this.props.history.push('/cart'))

3

quindi il modo in cui lo faccio è: - invece di reindirizzare usando history.push, io uso solo il Redirectcomponente da react-router-dom Quando uso questo componente puoi semplicemente passare push=truee si occuperà 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}/>
  }
}

questo è il corretto che non interrompe il ciclo di rendering con reazione
jzqa

1

Il router React V4 ora consente di utilizzare l'elica storica come di seguito:

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

È quindi possibile accedere al valore ovunque sia disponibile il puntello di posizione come state:{value}non stato del componente.


1
Questo non risponde alla domanda che si pone quale è come accedere a history.push ESTERNO di un componente. L'uso di this.props.history non è un'opzione per l'esterno di un componente.
Arkady,

0

puoi usarlo così come lo faccio per il login e manny cose diverse

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>

)

0
/*Step 1*/
myFunction(){  this.props.history.push("/home"); }
/**/
 <button onClick={()=>this.myFunction()} className={'btn btn-primary'}>Go 
 Home</button>

Non sono necessarie importazioni!
David Bagdasaryan,

1
Mentre questo codice può rispondere alla domanda, fornendo un contesto aggiuntivo riguardo al perché e / o al modo in cui questo codice risponde alla domanda migliora il suo valore a lungo termine.
rollstuhlfahrer

0

passo uno avvolgere la tua app nel router

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

Ora la mia intera App avrà accesso a BrowserRouter. Passo 2 Importare Route e poi tramandare quegli oggetti di scena. Probabilmente in uno dei tuoi file principali.

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>
      )}
    />

Ora, se eseguo console.log (this.props) nel mio file js componente, dovrei ottenere qualcosa che assomiglia a questo

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

Passaggio 2 Posso accedere all'oggetto della cronologia per modificare la mia posizione

//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

Inoltre sono solo uno studente di bootcamp in codice, quindi non sono un esperto, ma so che puoi anche usare

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

Correggimi se sbaglio, ma quando l'ho provato ha ricaricato l'intera pagina che pensavo avesse sconfitto l'intero punto di usare React.


0

Crea un'abitudine Routercon la sua 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

Successivamente, sulla radice in cui si definisce il proprio Router, utilizzare quanto segue:

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>
    )
  }
}

Infine, importa historydove ti serve e usalo:

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

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

0

Se si desidera utilizzare la cronologia mentre si passa una funzione come valore all'elica di un componente, con reagente-router 4 è possibile semplicemente destrutturare l' historyelica nell'attributo render del <Route/>componente e quindi utilizzarehistory.push()

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

Nota: affinché ciò funzioni, avvolgere il componente BrowserRouter di React Router attorno al componente radice (ad es. Che potrebbe trovarsi in index.js)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.