Navegue programaticamente usando o roteador de reação V4

336

Acabei de substituir react-routerda v3 para a v4.
Mas não sei como navegar programaticamente na função de membro de a Component. ou seja, na handleClick()função que deseja navegar /path/some/whereapós o processamento de alguns dados. Eu costumava fazer isso:

import { browserHistory } from 'react-router'
browserHistory.push('/path/some/where')

Mas não consigo encontrar essas interfaces na v4.
Como posso navegar usando a v4?

Colin Witkamp
fonte
3
Você pode acessar o objeto de histórico na v4 por meio de props: this.props.history.push ('/')
colemerrick
6
E se você quiser acessá-lo de um lugar diferente de um Component? Por exemplo, dentro das ações de redux.
Maximus S
73
Às vezes eu me perguntando por que é tão complicado para apenas mover de um link para os outros =))
Thai Tran
12
Alguém poderia pensar que nos dias de hoje o redirecionamento não seria tão complicado.
JohnAllen
Eu encontrei este post útil: medium.com/@anneeb/redirecting-in-react-4de5e517354a
BioData41

Respostas:

413

Se você estiver direcionando para ambientes de navegador, precisará usar o react-router-dompacote, em vez de react-router. Eles estão seguindo a mesma abordagem que Reagir fez, a fim de separar o núcleo, ( react) eo código de plataforma específica, ( react-dom, react-native), com a sutil diferença que você não precisa instalar dois pacotes separados, de modo que os pacotes ambiente contêm tudo você precisa. Você pode adicioná-lo ao seu projeto como:

yarn add react-router-dom

ou

npm i react-router-dom

A primeira coisa que você precisa fazer é fornecer <BrowserRouter>o componente principal como principal no aplicativo. <BrowserRouter>usa a historyAPI HTML5 e a gerencia para você, para que você não precise se preocupar em instanciar você mesmo e transmiti-lo ao <BrowserRouter>componente como um suporte (como era necessário nas versões anteriores).

Na V4, para navegar programaticamente, é necessário acessar o historyobjeto, disponível através do React context, desde que você tenha um componente de <BrowserRouter> provedor como o principal pai no aplicativo. A biblioteca expõe através do contexto o routerobjeto que ele próprio contém historycomo uma propriedade. A historyinterface oferece vários métodos de navegação, como push, replacee goBack, entre outros. Você pode verificar a lista completa de propriedades e métodos aqui .

Nota importante para usuários Redux / Mobx

Se você estiver usando redux ou mobx como sua biblioteca de gerenciamento de estado em seu aplicativo, poderá ter problemas com componentes que devem reconhecer o local, mas não são renderizados novamente depois de acionar uma atualização de URL

Isso está acontecendo porque react-routerpassa locationpara os componentes usando o modelo de contexto.

Tanto o connect quanto o observador criam componentes cujos métodos shouldComponentUpdate fazem uma comparação superficial dos adereços atuais e dos adereços seguintes. Esses componentes serão renderizados novamente somente quando pelo menos um suporte for alterado. Isso significa que, para garantir que eles sejam atualizados quando o local mudar, eles precisarão de um suporte que mude quando o local mudar.

As 2 abordagens para resolver isso são:

  • Enrole o componente conectado em um caminho sem caminho <Route />. O locationobjeto atual é um dos adereços que a <Route>passa para o componente que ele renderiza
  • Enrole o componente conectado com o withRoutercomponente de ordem superior, que de fato tem o mesmo efeito e injeta locationcomo suporte

Deixando isso de lado, há quatro maneiras de navegar programaticamente, ordenadas por recomendação:

1.- Usando um <Route>componente

Promove um estilo declarativo. Antes da v4, os <Route />componentes eram colocados no topo da hierarquia de componentes, tendo que pensar na estrutura de suas rotas com antecedência. No entanto, agora você pode ter <Route>componentes em qualquer lugar da sua árvore, permitindo um controle mais preciso da renderização condicional, dependendo da URL. Routeinjeta match, locatione historycomo adereços em seu componente. Os métodos de navegação (tais como push, replace, goBack...) estão disponíveis como as propriedades do historyobjecto.

Existem três maneiras de renderizar algo com a Route, usando ad component, renderou childrenadereços, mas não use mais de um no mesmo Route. A escolha depende do caso de uso, mas basicamente as duas primeiras opções renderizarão seu componente apenas se pathcorresponder ao local da URL, enquanto que childreno componente será renderizado se o caminho corresponder ao local ou não (útil para ajustar a interface do usuário com base no URL Coincidindo).

Se você quiser personalizar a sua saída de renderização de componente , você precisa envolver sua componente em uma função e usar a renderopção, a fim de passar para o seu componente quaisquer outros adereços que você deseja, para além de match, locatione history. Um exemplo para ilustrar:

import { BrowserRouter as Router } from 'react-router-dom'

const ButtonToNavigate = ({ title, history }) => (
  <button
    type="button"
    onClick={() => history.push('/my-new-location')}
  >
    {title}
  </button>
);

const SomeComponent = () => (
  <Route path="/" render={(props) => <ButtonToNavigate {...props} title="Navigate elsewhere" />} />
)    

const App = () => (
  <Router>
    <SomeComponent /> // Notice how in v4 we can have any other component interleaved
    <AnotherComponent />
  </Router>
);

2.- Usando withRouterHoC

Esse componente de ordem superior injeta os mesmos adereços que Route. No entanto, ele carrega a limitação de que você pode ter apenas 1 HoC por arquivo.

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

const ButtonToNavigate = ({ history }) => (
  <button
    type="button"
    onClick={() => history.push('/my-new-location')}
  >
    Navigate
  </button>
);


ButtonToNavigate.propTypes = {
  history: React.PropTypes.shape({
    push: React.PropTypes.func.isRequired,
  }),
};

export default withRouter(ButtonToNavigate);

3.- Usando um Redirectcomponente

A renderização de um <Redirect>navegará para um novo local. Mas lembre-se de que, por padrão , o local atual é substituído pelo novo, como redirecionamentos do lado do servidor (HTTP 3xx). O novo local é fornecido pelo toprop, que pode ser uma sequência (URL para a qual redirecionar) ou um locationobjeto. Se você deseja inserir uma nova entrada no histórico , passe também um pushsuporte e defina-o comotrue

<Redirect to="/your-new-location" push />

4.- Acessando routermanualmente através do contexto

Um pouco desencorajado porque o contexto ainda é uma API experimental e é provável que quebre / mude em versões futuras do React

const ButtonToNavigate = (props, context) => (
  <button
    type="button"
    onClick={() => context.router.history.push('/my-new-location')}
  >
    Navigate to a new location
  </button>
);

ButtonToNavigate.contextTypes = {
  router: React.PropTypes.shape({
    history: React.PropTypes.object.isRequired,
  }),
};

Escusado será dizer que também existem outros componentes do roteador destinados a ecossistemas que não são navegadores, como o <NativeRouter>que replica uma pilha de navegação na memória e tem como alvo a plataforma React Native, disponível através do react-router-nativepacote.

Para qualquer referência adicional, não hesite em dar uma olhada nos documentos oficiais . Há também um vídeo feito por um dos co-autores da biblioteca que fornece uma introdução bastante interessante ao reag-router v4, destacando algumas das principais mudanças.

rgommezz
fonte
2
Estou usando o V4 e o acima funciona bem. Passei um bom tempo mexendo no roteador V4, pois parece haver algumas escolhas estranhas, mas o que foi dito acima definitivamente funciona. Eu suponho que você está importando withRouterdereact-router-dom
Sam Parmenter
3
Estou importando withRouterde react-router-dom. O history.push muda o url, mas não parece carregar o <Route>até eu forçar atualizar a página ...
BreakDS
11
@rauliyohmc Eu estava errado sobre isso. O problema é que eu tenho <Router> dentro de um componente React que está decorado @observer, o que desencadeia esse problema . A solução é ter @withRouterem cada componente do React.
BreakDS
3
Para quem se deparar com essa ótima resposta, withRouteré simplesmente um HOC que usa um Routesob o capô. O que significa que ele usa apenas três objetos, histórico, correspondência e local . No exemplo acima, parece que pushé um adereço que withRouteradicionaria ButtonToNavigate, mas esse não é o caso. props.history.pushteria que ser usado em seu lugar. Espero que isso ajude outras pessoas um pouco confusas.
Vinnymac 23/03
39
Uau. browserHistory.push('/path/some/where')apenas parece muito mais simples. Os autores estão tentando desencorajar a programação imperativa, mas às vezes funciona melhor!
Lux
149

A maneira mais fácil de fazer isso:

this.props.history.push("/new/url")

Nota:

  • Você pode passar o history propcomponente-pai from para o componente que deseja chamar a ação, se não estiver disponível.
Jikku Jose
fonte
10
Eu uso o roteador 4.0, mas não há chave de histórico em adereços. Como posso corrigir isso?
Nicolas S.Xu / 01/17
41
Se você não tiver this.props.historydisponível em seu componente, poderá import {withRouter} from 'react-router-dom'e então export default withRouter(MyComponent)(ou const MyComponent = withRouter(...)) e ele inserirá o historyitem nos acessórios do seu componente.
Malvineous
@ Malvineous isso é interessante, não sabia disso! Vai tentar!
Jikku Jose 28/02
2
Devo estar faltando algo fundamental, porque isso funciona muito bem para mim, então não tenho idéia do porquê de todas as respostas exigirem explicações tão longas.
Joshua Pinter
como prevenir remount componente on history.pushe atualização gatilho assim como quando clicamos<Link>
Rahul Yadav
55

Eu tive um problema semelhante ao migrar para o React-Router v4, então tentarei explicar minha solução abaixo.

Por favor, não considere esta resposta como a maneira correta de resolver o problema, imagino que haja uma boa chance de que algo melhor surja à medida que o React Router v4 se tornar mais maduro e sair do beta (ele pode até já existir e eu simplesmente não o descobri) .

Por contexto, tive esse problema porque ocasionalmente uso Redux-Sagapara alterar programaticamente o objeto de histórico (por exemplo, quando um usuário se autentica com êxito).

Nos documentos do React Router, dê uma olhada no <Router> componente e você poderá ver que é capaz de transmitir seu próprio objeto de histórico por meio de um suporte. Essa é a essência da solução - fornecemos o objeto de histórico a React-Routerpartir de um módulo global .

Passos:

  1. Instale o módulo history npm - yarn add history ou npm install history --save
  2. crie um arquivo chamado history.jsna sua App.jspasta de nível (essa era minha preferência)

    // src/history.js
    
    import createHistory from 'history/createBrowserHistory';
    export default createHistory();`
  3. Adicione esse objeto de história ao seu componente Router, desta forma

    // src/App.js
    
    import history from '../your/path/to/history.js;'
    <Router history={history}>
    // Route tags here
    </Router>
  4. Ajuste o URL como antes, importando seu objeto de histórico global:

    import history from '../your/path/to/history.js;'
    history.push('new/path/here/');

Tudo deve ficar sincronizado agora, e você também tem acesso a uma maneira de definir o objeto histórico programaticamente e não por meio de um componente / contêiner.

Alex Mann
fonte
5
Essa mudança funcionou para mim, mas apenas porque estou navegando fora de um componente. Se eu estivesse navegando dentro de um componente como o OP, usaria a abordagem sugerida por @rauliyohmc usando os adereços passados ​​pelo Routecomponente.
Dan Ellis
2
esta é a abordagem recomendada em 17/08
Spets 18/08/19
2
@ Spets No meu caso, se eu usar esse tipo de abordagem, o link será atualizado corretamente após o envio, mas os componentes não serão renderizados corretamente (por exemplo, após a atualização do link, o componente não será atualizado a menos que você force a atualização da página). Onde você achou que essa abordagem é recomendada? Algum link / fonte?
arrefecer
2
@ScottCoates Eu resolvi usando o exemplo acima, na verdade fornecendo o histórico como parâmetro, mas depois eu depurei os módulos do nó sozinho. Há um erro comum cometido em toda a web, usando a importação de "BrowserHistory as Router", quando, em vez disso, existe outro objeto chamado Router sozinho na versão mais recente do react-router-dom. Usar isso em combinação com o histórico criado no exemplo acima funciona muito bem.
arrefecer
2
o URL é atualizado, mas a página não é renderizada com base na nova raiz. Qualquer solução? Por que rota de gatilho é tão difícil? As pessoas que projetam reagem estão loucas?
Nicolas S.Xu
43

TL; DR:

if (navigate) {
  return <Redirect to="/" push={true} />
}

A resposta simples e declarativa é que você precisa usar <Redirect to={URL} push={boolean} />em combinação comsetState()

push: boolean - quando verdadeiro, o redirecionamento enviará uma nova entrada para o histórico em vez de substituir a atual.


import { Redirect } from 'react-router'

class FooBar extends React.Component {
  state = {
    navigate: false
  }

  render() {
    const { navigate } = this.state

    // here is the important part
    if (navigate) {
      return <Redirect to="/" push={true} />
    }
   // ^^^^^^^^^^^^^^^^^^^^^^^

    return (
      <div>
        <button onClick={() => this.setState({ navigate: true })}>
          Home
        </button>
      </div>
    )
  }
}

Exemplo completo aqui . Leia mais aqui .

PS. O exemplo usa inicializadores de propriedades do ES7 + para inicializar o estado. Olhe aqui também, se você estiver interessado.

Lyubomir
fonte
5
Essa resposta deve ser aceita. Solução mais simples e elegante! +1 para @lustoykov
Ali Abbas Jaffri 15/17
11
Também defino navegar de volta para false em componentDidUpdate, pois meu botão está em um cabeçalho e, caso contrário, navegaria apenas uma vez.
peterorum 20/09/17
Isso funciona apenas se você souber sobre o redirecionamento quando a página for carregada. Se você estiver aguardando o retorno de uma chamada assíncrona (ou seja, autenticação com o Google ou algo assim), precisará usar um dos history.push()métodos.
brittohalloran
Na verdade, você ainda pode aproveitar a natureza declarativa da reação combinada com o componente <Redirect />. Se a página não carregar, você poderá fazer o fallback para outro <Redirect />
Lyubomir
@brittohallor A ideia desse método usando o roteador de reação adequado é garantir que você use setState para forçar uma nova renderização.
Alguém Especial
15

Use useHistorygancho se você estiver usando componentes de função

Você pode usar o useHistoryhook para obter a historyinstância.

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

const MyComponent = () => {
  var history = useHistory();

  return (
    <button onClick={() => history.push("/about")}>
      Click me
    </button>
  );
}

O useHistorygancho fornece acesso à instância do histórico que você pode usar para navegar.

Usar historypropriedade dentro dos componentes da página

O React Router injeta algumas propriedades, incluindo historya página de componentes.

class HomePage extends React.Component {
  render() {
    const { history } = this.props;

    return (
      <div>
        <button onClick={() => history.push("/projects")}>
          Projects
        </button>
      </div>
    );
  }
}

Quebrar componentes filho withRouterpara injetar propriedades do roteador

withRouterwrapper injeta propriedades do roteador nos componentes. Por exemplo, você pode usar esse invólucro para injetar o roteador no componente do botão de logout colocado no menu do usuário.

import { withRouter } from "react-router";

const LogoutButton = withRouter(({ history }) => {
  return (
    <button onClick={() => history.push("/login")}>
      Logout
    </button>
  );
});

export default LogoutButton;
TheMisir
fonte
11

Você também pode simplesmente usar adereços para acessar o objeto de histórico: this.props.history.push('new_url')

user1445685
fonte
5
Útil apenas quando o componente desce diretamente do roteador. Para que você não passe o suporte de histórico para todos os componentes nos quais você precisa dessa funcionalidade.
mwieczorek
3
Se você não tiver this.props.historydisponível em seu componente, poderá import {withRouter} from 'react-router-dom'e então export default withRouter(MyComponent)(ou const MyComponent = withRouter(...)) e ele inserirá o historyitem nos acessórios do seu componente.
Malvineous
9

Etapa 1: existe apenas uma coisa para importar na parte superior:

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

Etapa 2: em sua rota, passe o histórico:

<Route
  exact
  path='/posts/add'
  render={({history}) => (
    <PostAdd history={history} />
  )}
/>

Etapa 3: o histórico é aceito como parte dos acessórios no próximo componente, para que você possa simplesmente:

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

Isso foi fácil e muito poderoso.

cdi-meo
fonte
7

Isso funciona:

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

const SomeComponent = withRouter(({ history }) => (
    <div onClick={() => history.push('/path/some/where')}>
        some clickable element
    </div>); 
);

export default SomeComponent;
Kaya Toast
fonte
5

Minha resposta é semelhante à de Alex . Não sei por que o React-Router tornou isso tão desnecessariamente complicado. Por que devo envolver meu componente com um HoC apenas para ter acesso ao que é essencialmente global?

De qualquer forma, se você der uma olhada em como elas foram implementadas <BrowserRouter>, é apenas um pequeno invólucro da história .

Podemos extrair esse histórico para importá-lo de qualquer lugar. O truque, no entanto, é que, se você estiver processando no lado do servidor e tentar importo módulo de histórico, ele não funcionará porque usa APIs somente do navegador. Mas tudo bem, porque geralmente redirecionamos apenas em resposta a um clique ou outro evento do lado do cliente. Portanto, provavelmente não há problema em falsificá-lo:

// history.js
if(__SERVER__) {
    module.exports = {};
} else {
    module.exports = require('history').createBrowserHistory();
}

Com a ajuda do webpack, podemos definir alguns vars para saber em que ambiente estamos:

plugins: [
    new DefinePlugin({
        '__SERVER__': 'false',
        '__BROWSER__': 'true', // you really only need one of these, but I like to have both
    }),

E agora você pode

import history from './history';

De qualquer lugar. Ele retornará um módulo vazio no servidor.

Se você não quiser usar esses vars mágicos, precisará apenas requireno objeto global onde é necessário (dentro do manipulador de eventos). importnão funcionará porque funciona apenas no nível superior.

mpen
fonte
6
caramba, eles fizeram isso tão complicado.
precisa
4
Eu concordo totalmente com você. isso é tão complicado para apenas uma navegação #
Thai Tran
5

Eu acho que o @rgommezz cobre a maioria dos casos menos um que eu acho muito importante.

// history is already a dependency or React Router, but if don't have it then try npm install save-dev history

import createHistory from "history/createBrowserHistory"

// in your function then call add the below 
const history = createHistory();
// Use push, replace, and go to navigate around.
history.push("/home");

Isso me permite escrever um serviço simples com ações / chamadas que posso chamar para fazer a navegação de qualquer componente que eu quiser, sem fazer muito HoC em meus componentes ...

Não está claro por que ninguém forneceu essa solução antes. Espero que ajude, e se você encontrar algum problema com isso, entre em contato.

user3053247
fonte
4

Estou testando a v4 há alguns dias e estou adorando até agora! Faz sentido depois de um tempo.

Eu também tive a mesma pergunta e achei que lidar com ela da seguinte forma funcionou melhor (e pode até ser como ele se destina). Ele usa state, um operador ternário e <Redirect>.

No construtor ()

this.state = {
    redirectTo: null
} 
this.clickhandler = this.clickhandler.bind(this);

Na renderização ()

render(){
    return (
        <div>
        { this.state.redirectTo ?
            <Redirect to={{ pathname: this.state.redirectTo }} /> : 
            (
             <div>
               ..
             <button onClick={ this.clickhandler } />
              ..
             </div>
             )
         }

No manipulador de cliques ()

 this.setState({ redirectTo: '/path/some/where' });

Espero que ajude. Avise-se me.

jar0m1r
fonte
Existem armadilhas com este método? No meu projeto, funciona apenas quando defino o estado no construtor, a partir daí posso redirecionar para qualquer página que desejar. Mas quando eu definir o estado de evento (adereços fez a mudança, por exemplo) eu vejo método chamado com novo estado render, mas redirecionamento não acontecem vejo mesma página
Dmitry Malugin
A armadilha é que ele substituirá a história, para que você não possa revidar - então, basicamente, essa não é uma boa solução.
Dannytenaglias 6/09/17
4

Eu lutei com isso por um tempo - algo tão simples, mas tão complicado, porque o ReactJS é apenas uma maneira completamente diferente de escrever aplicativos da Web, é muito estranho para nós, pessoas mais velhas!

Eu criei um componente separado para abstrair a bagunça:

// LinkButton.js

import React from "react";
import PropTypes from "prop-types";
import {Route} from 'react-router-dom';

export default class LinkButton extends React.Component {

    render() {
        return (
            <Route render={({history}) => (
                <button {...this.props}
                       onClick={() => {
                           history.push(this.props.to)
                       }}>
                    {this.props.children}
                </button>
            )}/>
        );
    }
}

LinkButton.propTypes = {
    to: PropTypes.string.isRequired
};

Em seguida, adicione-o ao seu render()método:

<LinkButton className="btn btn-primary" to="/location">
    Button Text
</LinkButton>
mwieczorek
fonte
Acho esta solução bastante útil. Estou copiando o código. Deixe-me saber que você o colocou no github - eu o atribuirei diretamente.
Abhinav Manchanda
3

Como não há outra maneira de lidar com esse design horrível, escrevi um componente genérico que usa a abordagem withRouter HOC . O exemplo abaixo está agrupando um buttonelemento, mas você pode alterar para qualquer elemento clicável necessário:

import React from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';

const NavButton = (props) => (
  <Button onClick={() => props.history.push(props.to)}>
    {props.children}
  </Button>
);

NavButton.propTypes = {
  history: PropTypes.shape({
    push: PropTypes.func.isRequired
  }),
  to: PropTypes.string.isRequired
};

export default withRouter(NavButton);

Uso:

<NavButton to="/somewhere">Click me</NavButton>
Rodrigo
fonte
3
this.props.history.push("/url")

Se você não encontrou this.props.history disponível em seu componente, tente este

import {withRouter} from 'react-router-dom'
export default withRouter(MyComponent)  
Shashwat Gupta
fonte
Muito obrigado!
QauseenMZ 28/01
1

Como às vezes eu prefiro alternar rotas por aplicativo e depois por botões, este é um exemplo de trabalho mínimo que funciona para mim:

import { Component } from 'react'
import { BrowserRouter as Router, Link } from 'react-router-dom'

class App extends Component {
  constructor(props) {
    super(props)

    /** @type BrowserRouter */
    this.router = undefined
  }

  async handleSignFormSubmit() {
    await magic()
    this.router.history.push('/')
  }

  render() {
    return (
      <Router ref={ el => this.router = el }>
        <Link to="/signin">Sign in</Link>
        <Route path="/signin" exact={true} render={() => (
          <SignPage onFormSubmit={ this.handleSignFormSubmit } />
        )} />
      </Router>
    )
  }
}
piotr_cz
fonte
0

Para aqueles que precisam redirecionar antes de inicializar totalmente um roteador usando React Routerou React Router DomVocê pode fornecer um redirecionamento simplesmente acessando o objeto de histórico e pressionando um novo estado dentro de sua construção app.js. Considere o seguinte:

function getSubdomain(hostname) {
    let regexParse = new RegExp('[a-z\-0-9]{2,63}\.[a-z\.]{2,5}$');
    let urlParts = regexParse.exec(hostname);
    return hostname.replace(urlParts[0], '').slice(0, -1);
}

class App extends Component {

    constructor(props) {
        super(props);


        this.state = {
            hostState: true
        };

        if (getSubdomain(window.location.hostname).length > 0) {
            this.state.hostState = false;
            window.history.pushState('', '', './login');
        } else {
            console.log(getSubdomain(window.location.hostname));
        }

    }


    render() {
        return (

            <BrowserRouter>
                {this.state.hostState ? (
                    <div>
                        <Route path="/login" component={LoginContainer}/>
                        <Route path="/" component={PublicContainer}/>
                    </div>
                ) : (
                    <div>
                        <Route path="/login" component={LoginContainer}/>
                    </div>
                )

                }
            </BrowserRouter>)
    }


}

Aqui, queremos alterar as rotas de saída dependentes de um subdomínio, interagindo com o objeto de histórico antes que o componente seja processado, podemos redirecionar efetivamente enquanto ainda mantemos nossas rotas intactas.

window.history.pushState('', '', './login');
li x
fonte