Como passar para o History no React Router v4?

361

Na versão atual do React Router (v3), posso aceitar uma resposta do servidor e usar browserHistory.pushpara ir para a página de resposta apropriada. No entanto, isso não está disponível na v4 e não tenho certeza de qual é a maneira apropriada de lidar com isso.

Neste exemplo, usando o Redux, components / app-product-form.js chama this.props.addProduct(props)quando um usuário envia o formulário. Quando o servidor retorna com sucesso, o usuário é levado para a página Carrinho.

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

Como posso redirecionar para a página Carrinho da função React Router v4?

Chris
fonte
Só para adicionar isso à última solução oferecida e às sugestões dos problemas do React Router no GitHub, usar o contextpara passar o que você precisa manualmente é um "não-vá". A menos que eu seja um autor de biblioteca, não deve haver necessidade de usá-lo. De fato, o Facebook recomenda contra isso.
22417 Chris
@ Chris, você encontrou uma solução para isso? Eu preciso empurrar para um componente diferente em ação, mesmo que você tenha explicado aqui
Mr.G
@ Mr.G, infelizmente não tenho. A última vez que li foi a equipe do React Training que mantém o React Router com um pacote redux disponível. Não tive sorte em fazê-lo funcionar e eles não deram muito trabalho para resolvê-lo.
21717 Chris
Por que não podemos usar o windows.location.href = URL? Existe algo errado em usá-lo para alterar o URL e redirecionar?
Shan
Não vejo por que não, mas a opção de uso historytambém funciona para o React Native como uma opção, além de uma opção adicional de suporte a navegadores herdados.
Chris

Respostas:

308

Você pode usar os historymétodos fora dos seus componentes. Tente da seguinte maneira.

Primeiro, crie um historyobjeto usado no pacote de histórico :

// src/history.js

import { createBrowserHistory } from 'history';

export default createBrowserHistory();

Em seguida, envolva-o <Router>( observe , você deve usar em import { Router }vez 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'),
);

Altere sua localização atual de qualquer lugar, por exemplo:

// src/actions/userActionCreators.js

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

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

UPD : Você também pode ver um exemplo um pouco diferente nas Perguntas frequentes sobre o React Router .

Oleg Belostotsky
fonte
24
Tentei fazer exatamente o que o @OlegBelostotsky disse, mas depois history.push('some path')o URL muda, mas a página não muda. Eu tenho que colocá- window.location.reload()lo em algumas partes do meu código apenas para fazê-lo funcionar. No entanto, em um exemplo, eu tenho que manter a árvore de estado redux, e recarregar a destrói. Alguma outra solução?
Sdabrutas
2
@idunno Tente usar o withRoutercomponente de ordem superior.
Oleg Belostotsky 26/03/19
Isso gerou um erro ao declarar: createBrowserHistory não é uma função. O que eu posso fazer?
AKJ 13/11
@AKJ Talvez import createHistory from 'history/createBrowserHistory'; export default createHistory();seja trabalho.
Oleg Belostotsky
11
Desculpe pelo voto negativo :). Embora isso também deva funcionar, a maneira correta de lidar com isso é como Chris respondeu: stackoverflow.com/a/42716055/491075 .
gion_13 7/06/19
340

O React Router v4 é fundamentalmente diferente da v3 (e anterior) e você não pode fazer o browserHistory.push()que costumava fazer.

Esta discussão parece relacionada se você quiser obter mais informações:

  • Criar um novo browserHistorynão funcionará porque <BrowserRouter>cria sua própria instância do histórico e escuta as alterações. Portanto, uma instância diferente alterará o URL, mas não atualizará o <BrowserRouter>.
  • browserHistory não é exposto pelo react-router na v4, apenas na v2.

Em vez disso, você tem algumas opções para fazer isso:

  • Use o withRoutercomponente de alta ordem

    Em vez disso, você deve usar o withRoutercomponente de ordem superior e agrupá-lo no componente que será enviado ao histórico. Por exemplo:

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

    Confira a documentação oficial para mais informações:

    Você pode obter acesso ao historypropriedades do objeto e o mais próximo <Route>é matchatravés do componente de ordem superior withRouter. withRouter vai voltar a tornar a sua componente cada vez que a rota muda com os mesmos adereços como <Route>tornar adereços: { match, location, history }.


  • Use a contextAPI

    Usar o contexto pode ser uma das soluções mais fáceis, mas, sendo uma API experimental, é instável e sem suporte. Use-o apenas quando tudo mais falhar. Aqui está um exemplo:

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

    Veja a documentação oficial sobre o contexto:

    Se você deseja que seu aplicativo seja estável, não use o contexto. É uma API experimental e é provável que ocorra em versões futuras do React.

    Se você insistir em usar o contexto, apesar desses avisos, tente isolar o uso do contexto em uma pequena área e evite usar a API de contexto diretamente quando possível, para facilitar a atualização quando a API for alterada.

Chris
fonte
9
Sim, eu tentei. Obrigado por perguntar. :-) Então, como você coloca o contexto nessa função de ação? Até agora, está chegando como indefinido.
22417 Chris
11
Estou pesquisando esse tópico há alguns dias e não consegui fazê-lo funcionar. Mesmo usando o exemplo acima, eu continuo recebendo roteador é indefinido no contexto. Atualmente, estou usando o react v15.5.10, react-router-dom v4.1.1, prop-types 15.5.10. A documentação em torno disso é escassa e não muito clara.
Stu
5
@Stu isso deve funcionarthis.context.router.history.push('/path');
Tushar Khatiwada
11
@ Chris De fato, se você tentar instanciar o objeto histórico e usá-lo sozinho, ele mudará o URL, mas não renderizará o componente - como você mencionou acima. Mas como usar "history.push" fora do componente e forçar o componente renderizado também?
arrefecer
52
Isso não responde à pergunta que é como acessar o history.push FORA DE um componente. Usar withRouter ou o contexto não são opções para quando estiver fora de um componente.
precisa
49

Agora, com o react-router v5, você pode usar o gancho de ciclo de vida useHistory como este:

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

leia mais em: https://reacttraining.com/react-router/web/api/Hooks/usehistory

Hadi Abu
fonte
11
Essa é uma atualização muito bem-vinda! Obrigado!
24419 Chris
Existe alguma maneira específica de configurar isso, estou chamando o seguinte, let history = useHistory();mas obtendo um Object is not callableerro, quando tentei ver o que useHistory é console.log(useHistory)mostrado como indefinido. usando"react-router-dom": "^5.0.1"
steff_bdh
@steff_bdh, você precisa atualizá-lo no arquivo yout package.json para "react-router-dom": "^ 5.0.1" e executar 'npm install'
Hadi Abu
2
Nice, mas não pode usar gancho dentro de classes de ação redux, pois não são Reagir função do componente /
Jay
Como você usaria isso para redirecionar no login usando (assíncrono). Aqui está a pergunta => stackoverflow.com/questions/62154408/…
theairbend3r
29

A maneira mais simples do React Router 4 é usar

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

Mas, para usar esse método, seu componente existente deve ter acesso ao historyobjeto. Podemos ter acesso por

  1. Se o seu componente estiver vinculado Routediretamente, ele já terá acesso ao historyobjeto.

    por exemplo:

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

    Aqui ViewProfiletem acesso a history.

  2. Se não estiver conectado Routediretamente.

    por exemplo:

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

    Então temos que usar withRouteruma função de ordem superior para distorcer o componente existente.

    ViewUsersComponente interno

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

    • export default withRouter(ViewUsers);

    É isso agora, seu ViewUserscomponente tem acesso ao historyobjeto.

ATUALIZAR

2- nesse cenário, passe toda a rota propspara o seu componente e, em seguida, podemos acessar a this.props.historypartir do componente mesmo sem umHOC

por exemplo:

<Route path="/users" render={props => <ViewUsers {...props} />}
Saahithyan Vigneswaran
fonte
11
Excelente! Seu segundo método também fez o truque para mim, pois meu componente (que precisa de acesso this.props.history) vem de um HOC, o que significa que ele não está diretamente vinculado ao Route, como você explica.
cjauvin
25

Foi assim que eu fiz:

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

Use this.props.history.push('/cart');para redirecionar para a página do carrinho, ela será salva no objeto histórico.

Aproveite Michael.

Michael Horojanski
fonte
2
Sim, parece que, dentro dos componentes, você pode empurrar muito bem. A única maneira de afetar a navegação fora de um componente é com o redirecionamento.
Chris
14
Isso não responde à pergunta que é como acessar o history.push FORA DE um componente. Usar this.props.history não é uma opção para quando estiver fora de um componente.
precisa
22

De acordo com a documentação do React Router v4 - sessão Redux Deep Integration

É necessária uma profunda integração para:

"poder navegar enviando ações"

No entanto, eles recomendam essa abordagem como uma alternativa à "profunda integração":

"Em vez de enviar ações para navegar, você pode passar o objeto de histórico fornecido para rotear componentes para suas ações e navegar com ele até lá."

Assim, você pode agrupar seu componente com o componente de alta ordem withRouter:

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

que passará a API do histórico para adereços. Assim, você pode ligar para o criador da ação, passando o histórico como um parâmetro. Por exemplo, dentro do seu ReactComponent:

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

Em seguida, dentro de actions / index.js:

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

Questão desagradável, demorou bastante tempo, mas acabei resolvendo assim:

Envolva seu contêiner withRoutere passe o histórico para sua ação em mapDispatchToPropsfunção. Em ação, use history.push ('/ url') para navegar.

Açao:

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

Recipiente:

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

Isso é válido para Reagir Router v4.x .

Khrystyna Skvarok
fonte
Obrigado, sua solução withRouter funciona com texto datilografado, mas é bastante lenta em comparação com as anteriores, import { createBrowserHistory } from 'history' por favor?
Jay
7

this.context.history.push não funciona.

Eu consegui empurrar trabalhando assim:

static contextTypes = {
    router: PropTypes.object
}

handleSubmit(e) {
    e.preventDefault();

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

}
Aimar
fonte
9
Isso não responde à pergunta que é como acessar o history.push FORA DE um componente. Usar this.context não é uma opção para quando estiver fora de um componente.
precisa
6

Nesse caso, você está passando adereços para o seu thunk. Então você pode simplesmente ligar

props.history.push('/cart')

Se não for esse o caso, você ainda pode passar o histórico do seu componente

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

Eu ofereço mais uma solução, caso valha a pena para outra pessoa.

Eu tenho um history.jsarquivo em que tenho o seguinte:

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

Em seguida, na minha raiz, onde defino meu roteador, uso o seguinte:

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, no meu actions.jsimporto o History e uso o pushLater

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

Dessa forma, eu posso avançar para novas ações após chamadas de API.

Espero que ajude!

Diego
fonte
4

Se você estiver usando o Redux, recomendo usar o pacote npm react -router-redux . Permite enviar ações de navegação da loja Redux.

Você precisa criar uma loja conforme descrito no arquivo Leiame .

O caso de uso mais fácil:

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

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

Segundo caso de uso com contêiner / componente:

Recipiente:

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
    );
  }
}
Preto
fonte
11
Isso não funciona com o react-router-redux, a menos que você esteja usando a nextversão, que ainda está em desenvolvimento no momento!
Froginvasion
4

Consegui fazer isso usando bind(). Eu queria clicar em um botão index.jsx, postar alguns dados no servidor, avaliar a resposta e redirecionar para success.jsx. Aqui está como eu resolvi isso ...

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

Assim, através da ligação thisa postDatano index.jsx, eu era capaz de acesso this.props.historyem request.js... então eu posso reutilizar esta função em diferentes componentes, apenas tem que ter certeza que eu lembre-se de incluir this.postData = postData.bind(this)na constructor().

skwidbreth
fonte
3

Aqui está o meu hack (este é o meu arquivo de nível raiz, com um pouco de redux misturado lá - embora eu não esteja 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 então usar o local window.appHistory.push()que quiser (por exemplo, em minhas funções de loja redux / thunks / sagas, etc) que eu esperava poder usar, window.customHistory.push()mas, por algum motivo, react-routernunca parecia atualizar, mesmo que o URL mudasse. Mas desta forma eu tenho a instância EXACT react-routerusa. Eu não amo colocar coisas no escopo global, e essa é uma das poucas coisas com as quais eu faria isso. Mas é melhor do que qualquer outra alternativa que eu já vi na IMO.

João
fonte
3

Use retorno de chamada. Funcionou para mim!

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

No componente, basta adicionar o retorno de chamada

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

então, da maneira que faço, é: - em vez de redirecionar usando history.push, apenas uso o Redirectcomponente de react-router-dom Ao usar esse componente, você pode simplesmente passar push=truee ele cuidará do 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
fonte
este é o correto, o que não quebra reagir tornar ciclo
jzqa
1

O React Router V4 agora permite que o suporte de histórico seja usado como abaixo:

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

O valor pode ser acessado sempre que a localização prop estiver disponível como state:{value}estado não componente.

Snivio
fonte
11
Isso não responde à pergunta que é como acessar o history.push FORA DE um componente. Usar this.props.history não é uma opção para quando estiver fora de um componente.
Arkady
0

você pode usá-lo assim, como faço para fazer login e muitas coisas 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
fonte
0
/*Step 1*/
myFunction(){  this.props.history.push("/home"); }
/**/
 <button onClick={()=>this.myFunction()} className={'btn btn-primary'}>Go 
 Home</button>
David Bagdasaryan
fonte
Não há necessidade de importações!
precisa
11
Embora esse código possa responder à pergunta, fornecer um contexto adicional a respeito de por que e / ou como esse código responde à pergunta melhora seu valor a longo prazo.
Rollstuhlfahrer
0

primeiro passo, envolva seu aplicativo no roteador

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

Agora todo o meu aplicativo terá acesso ao BrowserRouter. Etapa 2: Importo o Route e, em seguida, passo os adereços. Provavelmente em um dos seus arquivos principais.

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

Agora, se eu executar o console.log (this.props) no meu arquivo js componente, devo obter algo parecido com isto

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

Etapa 2 Posso acessar o objeto histórico para alterar minha localização

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

Além disso, sou apenas um estudante de código de inicialização, então não sou especialista, mas sei que você também pode usar

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

Corrija-me se eu estiver errado, mas quando testei, recarreguei a página inteira, o que pensei ter derrotado o ponto inteiro de usar o React.


fonte
0

Crie uma customização Routerprópria 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

Em seguida, na sua raiz onde você a define Router, use o seguinte:

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

Por fim, importe historyonde você precisar e use:

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

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

Se você deseja usar o histórico ao passar uma função como um valor para o suporte de um componente, com o react-router 4, você pode simplesmente desestruturar o historysuporte no atributo de renderização do <Route/>componente e usarhistory.push()

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

Nota: Para que isso funcione, você deve agrupar o Componente BrowserRouter do React Router em torno do seu componente raiz (por exemplo, que pode estar em index.js)

Kewal Shah
fonte