Comment passer à l'historique dans React Router v4?


361

Dans la version actuelle de React Router (v3), je peux accepter une réponse du serveur et l'utiliser browserHistory.pushpour accéder à la page de réponse appropriée. Cependant, ce n'est pas disponible dans la v4, et je ne sais pas quelle est la façon appropriée de gérer cela.

Dans cet exemple, à l'aide de Redux, components / app-product-form.js appelle this.props.addProduct(props)lorsqu'un utilisateur soumet le formulaire. Lorsque le serveur renvoie un succès, l'utilisateur est redirigé vers la page Panier.

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

Comment puis-je faire une redirection vers la page Panier à partir de la fonction pour React Router v4?


Juste pour ajouter à cela à partir de la dernière solution proposée et des suggestions dans les problèmes de React Router sur GitHub, utiliser le contextpour passer ce dont vous avez besoin manuellement est un "no go". Sauf si je suis un auteur de bibliothèque, il ne devrait pas être nécessaire de l'utiliser. En fait, Facebook le déconseille.
Chris

@Chris avez-vous trouvé une solution à cela? J'ai besoin de pousser vers un autre composant en action, comme vous l'avez expliqué ici
Mr.G

@ Mr.G, malheureusement je ne l'ai pas. La dernière fois que j'ai lu, c'est l'équipe de formation React qui maintient que React Router a un package redux disponible. Je n'ai pas eu de chance pour le faire fonctionner, et ils n'ont pas beaucoup travaillé pour le résoudre.
Chris

Pourquoi ne pouvons-nous pas utiliser windows.location.href = URL? Y a-t-il quelque chose de mal à l'utiliser pour changer l'URL et rediriger?
Shan

Je ne vois pas pourquoi, mais l'option d'utilisation historyfonctionne également pour React Native comme option ainsi qu'une option supplémentaire de prise en charge des navigateurs hérités.
Chris

Réponses:


308

Vous pouvez utiliser les historyméthodes en dehors de vos composants. Essayez de la manière suivante.

Tout d'abord, créez un historyobjet utilisé le package historique :

// src/history.js

import { createBrowserHistory } from 'history';

export default createBrowserHistory();

Ensuite, enveloppez-le <Router>( veuillez noter , vous devez utiliser à la import { Router }place 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'),
);

Modifiez votre position actuelle à partir de n'importe quel endroit, par exemple:

// src/actions/userActionCreators.js

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

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

UPD : Vous pouvez également voir un exemple légèrement différent dans la FAQ React Router .


24
J'ai essayé de faire exactement ce qu'a dit @OlegBelostotsky, mais après history.push('some path'), l'URL change mais la page ne change pas. Je dois le mettre window.location.reload()dans certaines parties de mon code juste pour le faire fonctionner. Cependant, dans un cas, je dois conserver l'arborescence d'état redux et le rechargement le détruit. Une autre solution?
sdabrutas

2
@idunno Essayez d'utiliser le withRoutercomposant d'ordre supérieur.
Oleg Belostotsky

Cela m'a jeté une erreur indiquant: createBrowserHistory n'est pas une fonction. Que puis-je faire?
AKJ

@AKJ Peut import createHistory from 'history/createBrowserHistory'; export default createHistory();-être que ce sera du travail.
Oleg Belostotsky

1
Désolé pour le downvote :). Bien que cela devrait également fonctionner, la façon correcte de gérer cela est la réponse de Chris: stackoverflow.com/a/42716055/491075 .
gion_13

340

React Router v4 est fondamentalement différent de la v3 (et des versions antérieures) et vous ne pouvez plus faire browserHistory.push()comme avant.

Cette discussion semble liée si vous voulez plus d'informations:

  • La création d'un nouveau browserHistoryne fonctionnera pas car <BrowserRouter>crée sa propre instance d'historique et écoute les modifications à ce sujet. Une autre instance modifiera donc l'url mais ne mettra pas à jour le <BrowserRouter>.
  • browserHistory n'est pas exposé par react-router en v4, seulement en v2.

Au lieu de cela, vous avez quelques options pour ce faire:

  • Utilisez le withRoutercomposant de haut niveau

    Au lieu de cela, vous devez utiliser le withRoutercomposant de premier ordre et envelopper ce composant dans le composant qui passera à l'historique. Par exemple:

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

    Consultez la documentation officielle pour plus d'informations:

    Vous pouvez accéder aux historypropriétés de l' objet et le plus proche <Route>est matchvia le composant d'ordre supérieur withRouter. withRouter restitue son composant à chaque fois que la route change avec les mêmes accessoires que<Route> rendre les accessoires: { match, location, history }.


  • Utilisez l' contextAPI

    L'utilisation du contexte peut être l'une des solutions les plus simples, mais étant une API expérimentale, elle est instable et non prise en charge. Utilisez-le uniquement lorsque tout le reste échoue. Voici un exemple:

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

    Jetez un oeil à la documentation officielle sur le contexte:

    Si vous souhaitez que votre application soit stable, n'utilisez pas de contexte. Il s'agit d'une API expérimentale qui risque de se casser dans les futures versions de React.

    Si vous insistez pour utiliser le contexte malgré ces avertissements, essayez d'isoler votre utilisation du contexte sur une petite zone et évitez d'utiliser l'API de contexte directement lorsque cela est possible afin de faciliter la mise à niveau lorsque l'API change.


9
Oui, je l'ai essayé. Merci d'avoir posé la question. :-) Alors, comment obtenez-vous le contexte dans cette fonction d'action? Jusqu'à présent, cela arrive comme indéfini.
Chris

1
Je fais des recherches sur ce sujet depuis quelques jours et je n'ai pas réussi à le faire fonctionner. Même en utilisant l'exemple ci-dessus, je continue à obtenir un routeur non défini dans le contexte. J'utilise actuellement react v15.5.10, react-router-dom v4.1.1, prop-types 15.5.10. La documentation à ce sujet est rare et pas très claire.
Stu

5
@Stu cela devrait fonctionnerthis.context.router.history.push('/path');
Tushar Khatiwada

1
@Chris En effet, si vous essayez d'instancier un objet historique et de l'utiliser par vous-même, cela changera l'url mais ne rendra pas le composant - comme vous l'avez mentionné ci-dessus. Mais, comment utiliser "history.push" en dehors du composant et forcer le rendu du composant également?
cool

52
Cela ne répond pas à la question posée de savoir comment accéder à history.push EXTERIEUR d'un composant. L'utilisation de withRouter ou du contexte n'est pas une option lorsque vous êtes en dehors d'un composant.
SunshinyDoyle

49

Maintenant, avec react-router v5, vous pouvez utiliser le hook useHistory du cycle de vie comme ceci:

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

en savoir plus sur: https://reacttraining.com/react-router/web/api/Hooks/usehistory


1
C'est une mise à jour très bienvenue! Merci!
Chris

Existe-t-il un moyen spécifique de configurer cela, j'appelle ce qui suit, let history = useHistory();mais j'obtiens une Object is not callableerreur, lorsque j'ai essayé de voir à quoi useHistory console.log(useHistory)apparaît comme indéfini. en utilisant"react-router-dom": "^5.0.1"
steff_bdh

@steff_bdh vous devez le mettre à jour dans le fichier package.json vers "react-router-dom": "^ 5.0.1" et exécuter 'npm install'
Hadi Abu

2
Bien, mais ne peut pas utiliser le hook dans les classes d'actions redux car elles ne sont pas des composants / fonctions React
Jay

Comment utiliseriez-vous cela pour la redirection lors de la connexion à l'aide de (async). Voici la question => stackoverflow.com/questions/62154408/…
theairbend3r

29

Le moyen le plus simple dans React Router 4 est d'utiliser

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

Mais pour utiliser cette méthode, votre composant existant doit avoir accès à l' historyobjet. Nous pouvons avoir accès par

  1. Si votre composant est Routedirectement lié à , votre composant a déjà accès àhistory objet.

    par exemple:

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

    Ici ViewProfilea accès à history.

  2. S'il n'est pas connecté à Route directement.

    par exemple:

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

    Ensuite, nous devons utiliser withRouter une fonction d'ordre supérieur pour déformer le composant existant.

    ViewUsersComposant intérieur

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

    • export default withRouter(ViewUsers);

    Voilà, votre ViewUserscomposant a accès à l' historyobjet.

MISE À JOUR

2- dans ce scénario, passez toutes les routes propsvers votre composant, puis nous pourrons accéderthis.props.history partir du composant même sansHOC

par exemple:

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

1
Excellent! Votre deuxième méthode a également fait l'affaire pour moi, car mon composant (qui doit y accéder this.props.history) provient d'un HOC, ce qui signifie qu'il n'est pas directement lié au Route, comme vous l'expliquez.
cjauvin

25

Voici comment je l'ai fait:

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

Utilisation this.props.history.push('/cart'); pour rediriger vers la page du panier, il sera enregistré dans l'objet historique.

Profitez-en, Michael.


2
Oui, il semble que dans les composants, vous pouvez très bien pousser. La seule façon d'affecter la navigation en dehors d'un composant est la redirection.
Chris

14
Cela ne répond pas à la question posée de savoir comment accéder à history.push EXTERIEUR d'un composant. L'utilisation de this.props.history n'est pas une option lorsque vous êtes en dehors d'un composant.
SunshinyDoyle

22

Selon la documentation de React Router v4 - Session Redux Deep Integration

Une intégration profonde est nécessaire pour:

"être capable de naviguer en envoyant des actions"

Cependant, ils recommandent cette approche comme une alternative à "l'intégration profonde":

"Plutôt que d'envoyer des actions pour naviguer, vous pouvez passer l'objet historique fourni pour acheminer les composants vers vos actions et y naviguer avec lui."

Vous pouvez donc envelopper votre composant avec le composant de haut niveau withRouter:

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

qui passera l'API historique aux accessoires. Vous pouvez donc appeler le créateur d'action en passant l'historique comme paramètre. Par exemple, dans votre ReactComponent:

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

Ensuite, à l'intérieur de vos actions / index.js:

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

19

Question désagréable, cela m'a pris beaucoup de temps, mais finalement, je l'ai résolu de cette façon:

Enveloppez votre conteneur avec withRouteret passez l'historique à votre action mapDispatchToProps fonction. En action, utilisez history.push ('/ url') pour naviguer.

Action:

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

Récipient:

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

Ceci est valable pour React v4.x Router .


Merci, votre solution withRouter fonctionne avec dactylographié mais c'est assez lent par rapport à import { createBrowserHistory } from 'history' une idée antérieure s'il vous plaît?
Jay

7

this.context.history.push ne fonctionnera pas.

J'ai réussi à faire fonctionner push comme ceci:

static contextTypes = {
    router: PropTypes.object
}

handleSubmit(e) {
    e.preventDefault();

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

}

9
Cela ne répond pas à la question posée de savoir comment accéder à history.push EXTERIEUR d'un composant. L'utilisation de this.context n'est pas une option lorsque vous êtes en dehors d'un composant.
SunshinyDoyle

6

Dans ce cas, vous passez des accessoires à votre thunk. Vous pouvez donc simplement appeler

props.history.push('/cart')

Si ce n'est pas le cas, vous pouvez toujours transmettre l'historique de votre composant

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

6

J'offre une autre solution au cas où elle serait intéressante pour quelqu'un d'autre.

J'ai un history.jsdossier où j'ai les éléments suivants:

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

Ensuite, sur ma racine où je définis mon routeur, j'utilise ce qui suit:

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

Enfin, sur mon actions.jshistorique d'importation, j'utilise pushLater

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

De cette façon, je peux pousser vers de nouvelles actions après les appels d'API.

J'espère que cela aide!


4

Si vous utilisez Redux, je vous recommande d'utiliser le package npm react -router-redux . Il vous permet d'envoyer des actions de navigation dans le magasin Redux.

Vous devez créer un magasin comme décrit dans leur fichier Lisez-moi .

Le cas d'utilisation le plus simple:

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

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

Deuxième cas d'utilisation avec conteneur / composant:

Récipient:

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

Composant:

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
Cela ne fonctionne pas avec react-router-redux, sauf si vous utilisez la nextversion, qui est toujours en cours de développement en ce moment!
froginvasion

4

J'ai pu accomplir cela en utilisant bind(). Je voulais cliquer sur un bouton index.jsx, publier des données sur le serveur, évaluer la réponse et rediriger vers success.jsx. Voici comment j'ai travaillé cela ...

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

Donc , en me liant thisà postDatain index.jsx, j'ai pu accéder à this.props.historyin request.js... puis je peux réutiliser cette fonction dans différents composants, je dois juste m'assurer de ne pas oublier de l'inclure this.postData = postData.bind(this)dans le constructor().


3

Voici mon hack (c'est mon fichier de niveau racine, avec un peu de redux mélangé - bien que je n'utilise pas 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')
)

Je peux ensuite utiliser window.appHistory.push()n'importe où (par exemple, dans les fonctions / thunks / sagas de mon magasin redux), j'espérais pouvoir simplement utiliser, window.customHistory.push()mais pour une raison quelconque, je react-routern'ai jamais semblé mettre à jour même si l'URL avait changé. Mais de cette façon, j'ai l'instance EXACT react-routerutilise. Je n'aime pas mettre des choses à l'échelle mondiale, et c'est l'une des rares choses avec lesquelles je ferais ça. Mais c'est mieux que toute autre alternative que j'ai vue à l'OMI.


3

Utilisez le rappel. Ça a marché pour moi!

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

Dans le composant, il vous suffit d'ajouter le rappel

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

3

donc la façon dont je le fais est: - au lieu de rediriger en utilisant history.push, j'utilise simplement le Redirectcomposant de react-router-dom Lors de l'utilisation de ce composant, vous pouvez simplement passer push=true, et il s'occupera du reste

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

c'est le bon qui ne casse pas le cycle de rendu de la réaction
jzqa

1

React router V4 permet désormais d'utiliser l'accessoire d'historique comme ci-dessous:

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

La valeur peut alors être consultée partout où l'hélice de localisation est disponible en tant state:{value}qu'état non composant.


1
Cela ne répond pas à la question posée de savoir comment accéder à history.push EXTERIEUR d'un composant. L'utilisation de this.props.history n'est pas une option lorsque vous êtes en dehors d'un composant.
Arkady

0

vous pouvez l'utiliser comme ça comme je le fais pour la connexion et différentes choses manny

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>

Pas besoin d'importations!
David Bagdasaryan

1
Bien que ce code puisse répondre à la question, fournir un contexte supplémentaire concernant pourquoi et / ou comment ce code répond à la question améliore sa valeur à long terme.
rollstuhlfahrer

0

première étape envelopper votre application dans le routeur

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

Maintenant, mon application entière aura accès à BrowserRouter. Étape deux J'importe Route, puis je transmets ces accessoires. Probablement dans l'un de vos fichiers principaux.

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

Maintenant, si j'exécute console.log (this.props) dans mon fichier js de composant, je devrais obtenir quelque chose qui ressemble à ceci

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

Étape 2 Je peux accéder à l'objet historique pour modifier ma position

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

De plus, je ne suis qu'un étudiant en bootcamp de codage, donc je ne suis pas un expert, mais je sais que vous pouvez également utiliser

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

Corrigez-moi si je me trompe, mais quand j'ai testé cela, il a rechargé la page entière, ce qui, à mon avis, a vaincu l'intérêt d'utiliser React.


0

Créez une coutume Routeravec la sienne 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

Ensuite, sur votre racine où vous définissez votre Router, utilisez ce qui suit:

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

Enfin, importez historyoù vous en avez besoin et utilisez-le:

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

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

0

Si vous souhaitez utiliser l'historique tout en passant une fonction en tant que valeur à l'accessoire d'un composant, avec react-router 4, vous pouvez simplement détruire l' historyaccessoire dans l'attribut de rendu du <Route/>composant, puis utiliserhistory.push()

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

Remarque: pour que cela fonctionne, vous devez enrouler le composant BrowserRouter de React Router autour de votre composant racine (par exemple, qui pourrait être dans index.js)

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.