Как перейти к истории в React Router v4?

361

В текущей версии React Router (v3) я могу принять ответ сервера и использовать browserHistory.pushдля перехода на соответствующую страницу ответа. Тем не менее, это не доступно в v4, и я не уверен, как правильно это сделать.

В этом примере при использовании Redux компоненты / app-product-form.js вызывают, this.props.addProduct(props)когда пользователь отправляет форму. Когда сервер возвращает успех, пользователь попадает на страницу корзины.

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

Как я могу сделать перенаправление на страницу корзины из функции для React Router v4?

Крис
источник
Просто добавьте к этому из последнего предложенного решения и из предложений в проблемах React Router на GitHub, использование contextдля передачи того, что вам нужно, вручную - это «не пойдет». Если я не являюсь автором библиотеки, не должно быть необходимости его использовать. На самом деле, Facebook рекомендует против этого.
Крис
@ Крис, ты нашел решение для этого? мне нужно подтолкнуть к другому компоненту в действии, так же, как вы объяснили здесь
Mr.G
@ Mr.G, к сожалению, нет. Последнее, что я прочитал, была команда React Training, которая утверждает, что в React Router есть доступ к пакету с избыточностью. Мне не повезло заставить его работать, и они не приложили много усилий для его решения.
Крис
Почему мы не можем использовать windows.location.href = URL? Есть ли что-то неправильное в использовании его для изменения URL и перенаправления?
Shan
Я не понимаю, почему нет, но опция использования historyтакже работает для React Native как опция, а также опция поддержки устаревших браузеров.
Крис

Ответы:

308

Вы можете использовать historyметоды за пределами ваших компонентов. Попробуйте следующим образом.

Сначала создайте historyобъект, используя пакет истории :

// src/history.js

import { createBrowserHistory } from 'history';

export default createBrowserHistory();

Затем оберните его <Router>( обратите внимание , вы должны использовать import { Router }вместо 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'),
);

Измените свое текущее местоположение из любого места, например:

// src/actions/userActionCreators.js

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

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

UPD : Вы также можете увидеть немного другой пример в FAQ по React Router .

Олег Белостоцкий
источник
24
Я пытался сделать именно так, как сказал @OlegBelostotsky, но после history.push('some path')этого URL-адрес меняется, но страница не меняется. Я должен поместить window.location.reload()его в некоторые части своего кода, чтобы он работал. Тем не менее, в одном случае я должен сохранить избыточное дерево состояний, а перезагрузка уничтожает его. Любое другое решение?
Сдабрутас
2
@idunno Попробуйте использовать withRouterкомпонент высшего порядка.
Олег Белостоцкий,
Это вызвало у меня ошибку, утверждающую: createBrowserHistory не является функцией. Что я могу сделать?
AKJ
@AKJ Может быть, import createHistory from 'history/createBrowserHistory'; export default createHistory();будет работать.
Олег Белостоцкий
1
Извините за понижение голосов :). Хотя это также должно работать, правильный ответ - это ответ Криса: stackoverflow.com/a/42716055/491075 .
gion_13
340

React Router v4 принципиально отличается от v3 (и более ранних версий), и вы не можете делать то, browserHistory.push()что делали раньше.

Это обсуждение кажется связанным, если вы хотите больше информации:

  • Создание нового browserHistoryне будет работать, потому что <BrowserRouter>создает свой собственный экземпляр истории и прислушивается к изменениям. Таким образом, другой экземпляр изменит URL, но не обновит <BrowserRouter>.
  • browserHistory не выставляется реагирующим маршрутизатором в v4, только в v2.

Вместо этого у вас есть несколько вариантов сделать это:

  • Используйте withRouterкомпонент высокого порядка

    Вместо этого вы должны использовать withRouterкомпонент высшего порядка и перенести его в компонент, который будет перемещаться в историю. Например:

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

    Проверьте официальную документацию для получения дополнительной информации:

    Вы можете получить доступ к historyсвойствам объекта и ближайших к нему <Route>с matchпомощью компонента высшего порядка withRouter. withRouter будет вновь сделать свой компонент каждый раз , когда маршрут изменяется с тем же реквизитом , как <Route>делает реквизит: { match, location, history }.


  • Используйте contextAPI

    Использование контекста может быть одним из самых простых решений, но, будучи экспериментальным API, оно нестабильно и не поддерживается. Используйте это только когда все остальное терпит неудачу. Вот пример:

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

    Посмотрите на официальную документацию по контексту:

    Если вы хотите, чтобы ваше приложение было стабильным, не используйте контекст. Это экспериментальный API, и он может сломаться в будущих версиях React.

    Если вы настаиваете на использовании контекста, несмотря на эти предупреждения, постарайтесь изолировать использование контекста в небольшой области и избегайте использования контекстного API напрямую, когда это возможно, чтобы его было проще обновить при изменении API.

Крис
источник
9
Да, я попробовал это. Спасибо за вопрос. :-) Итак, как вы получаете контекст в этой функции действия? Пока что это выглядит как неопределенное.
Крис
1
Я изучал эту тему в течение нескольких дней и не смог заставить это работать. Даже используя приведенный выше пример, я продолжаю получать маршрутизатор не определен в контексте. В настоящее время я использую response v15.5.10, response-router-dom v4.1.1, prop-types 15.5.10. Документация вокруг этого редкая и не очень понятная.
Сту
5
@ Это должно сработатьthis.context.router.history.push('/path');
Тушар Хативада
1
@Chris Действительно, если вы попытаетесь создать экземпляр объекта истории и использовать его самостоятельно, он изменит URL, но не отобразит компонент - как вы упоминали выше. Но как использовать «history.push» за пределами компонента и рендера компонента, а также?
классно
52
Это не отвечает на вопрос о том, как получить доступ к history.push ВНЕ компонента. Использование withRouter или контекста не является опцией для того, чтобы находиться вне компонента.
SunshinyDoyle
49

Теперь с response-router v5 вы можете использовать ловушку жизненного цикла useHistory следующим образом:

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

читать больше на: https://reacttraining.com/react-router/web/api/Hooks/usehistory

Хади Абу
источник
1
Это очень долгожданное обновление! Спасибо!
Крис
Есть ли какой-то конкретный способ, который мне нужно настроить, я вызываю следующее, let history = useHistory();но получаю сообщение Object is not callableоб ошибке, когда я пытался увидеть, что такое useHistory, console.log(useHistory)оно отображается как неопределенное. используя"react-router-dom": "^5.0.1"
steff_bdh
@steff_bdh, вам нужно обновить его в файле yout package.json до «response-router-dom»: «^ 5.0.1» и запустить «npm install»
Хади Абу,
2
Хорошо, но нельзя использовать ловушку внутри классов действий редукса, так как они не являются компонентом / функцией React
Jay
Как бы вы использовали это для перенаправления при входе в систему (async). Вот вопрос => stackoverflow.com/questions/62154408/…
theairbend3r
29

Простейшим способом в React Router 4 является использование

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

Но чтобы использовать этот метод, ваш существующий компонент должен иметь доступ к historyобъекту. Мы можем получить доступ по

  1. Если ваш компонент связан с Routeнапрямую, то ваш компонент уже имеет доступ к historyобъекту.

    например:

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

    Здесь ViewProfileесть доступ к history.

  2. Если не подключен Routeнапрямую.

    например:

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

    Затем мы должны использовать withRouterфункцию более высокого порядка, чтобы деформировать существующий компонент.

    Внутри ViewUsers компонента

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

    • export default withRouter(ViewUsers);

    Теперь у вашего ViewUsersкомпонента есть доступ к historyобъекту.

ОБНОВИТЬ

2- в этом случае передайте весь маршрут propsк вашему компоненту, и тогда мы сможем получить доступ this.props.historyиз компонента даже безHOC

например:

<Route path="/users" render={props => <ViewUsers {...props} />}
Saahithyan Vigneswaran
источник
1
Отлично! Ваш второй метод также помог мне, поскольку мой компонент (к которому требуется доступ this.props.history) происходит из HOC, что означает, что он не связан напрямую с Route, как вы объясняете.
cjauvin
25

Вот как я это сделал:

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

Используйте this.props.history.push('/cart');для перенаправления на страницу корзины, она будет сохранена в истории объекта.

Наслаждайся, Майкл.

Михаил Хорожанский
источник
2
Да, похоже, что внутри компонентов вы можете просто выдвинуть. Единственный способ повлиять на навигацию вне компонента - это перенаправление.
Крис
14
Это не отвечает на вопрос о том, как получить доступ к history.push ВНЕ компонента. Использование this.props.history не допускается для использования вне компонента.
SunshinyDoyle
22

Согласно документации React Router v4 - сессия Redux Deep Integration

Глубокая интеграция необходима для:

«уметь ориентироваться, отправляя действия»

Однако они рекомендуют этот подход в качестве альтернативы "глубокой интеграции":

«Вместо того, чтобы отправлять действия для навигации, вы можете передать предоставленный объект истории, чтобы направить компоненты к вашим действиям и перемещаться с ним там».

Таким образом, вы можете обернуть ваш компонент компонентом высшего порядка withRouter:

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

который будет передавать историю API для реквизита. Таким образом, вы можете назвать создателя действия, передавая историю в качестве параметра. Например, внутри вашего ReactComponent:

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

Затем внутри ваших действий / index.js:

export function actionCreatorName(history, param) {
  return dispatch => {
    dispatch({
      type: SOME_ACTION,
      payload: param.data
    });
    history.push("/path");
  };
}
alainbex
источник
19

Непростой вопрос, занял у меня довольно много времени, но в итоге я решил его так:

Оберните свой контейнер withRouterи передайте историю в ваши действия в mapDispatchToPropsфункции. В действии используйте history.push ('/ url') для навигации.

Действие:

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

Контейнер:

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

Это справедливо для React v4.x маршрутизатора .

Кристина Скварок
источник
Спасибо, ваши withRouter решение работает с машинописи , но его довольно медленно по сравнению с ранее import { createBrowserHistory } from 'history' какой - либо идеи , пожалуйста?
Джей
7

this.context.history.push не будет работать.

Мне удалось получить толчок, работая так:

static contextTypes = {
    router: PropTypes.object
}

handleSubmit(e) {
    e.preventDefault();

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

}
Аймар
источник
9
Это не отвечает на вопрос о том, как получить доступ к history.push ВНЕ компонента. Использование this.context не является опцией для использования вне компонента.
SunshinyDoyle
6

В этом случае вы передаете реквизит своему толпу. Так что вы можете просто позвонить

props.history.push('/cart')

Если это не так, вы все равно можете передать историю из вашего компонента

export function addProduct(data, history) {
  return dispatch => {
    axios.post('/url', data).then((response) => {
      dispatch({ type: types.AUTH_USER })
      history.push('/cart')
    })
  }
}
user3812377
источник
6

Я предлагаю еще одно решение, если оно того стоит.

У меня есть history.jsфайл, где у меня есть следующее:

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

Далее, в моем Root, где я определяю свой маршрутизатор, я использую следующее:

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

Наконец, actions.jsя импортирую историю и использую pushLater

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

Таким образом, я могу перейти к новым действиям после вызовов API.

Надеюсь, поможет!

Диего
источник
4

Если вы используете Redux, то я бы порекомендовал использовать пакет npm response -router-redux . Позволяет отправлять в Redux магазин навигационных действий.

Вы должны создать магазин, как описано в их файле Readme .

Самый простой вариант использования:

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

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

Второй вариант использования с контейнером / компонентом:

Контейнер:

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

Составная часть:

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
Это не работает с реактивом-маршрутизатором-редуктом, если вы не используете nextверсию, которая все еще находится в стадии разработки!
froginvasion
4

Я смог сделать это с помощью bind(). Я хотел нажать кнопку index.jsx, опубликовать некоторые данные на сервере, оценить ответ и перенаправить на success.jsx. Вот как я это выяснил ...

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

Так что, связавшись thisс postDatain index.jsx, я смог получить доступ this.props.historyк request.js... тогда я могу повторно использовать эту функцию в разных компонентах, просто убедитесь, что я не забудьте включить ее this.postData = postData.bind(this)в constructor().

skwidbreth
источник
3

Вот мой хак (это мой файл корневого уровня, с небольшим добавленным там редуксом - хотя я не использую 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')
)

Затем я могу использовать в window.appHistory.push()любом месте, где захочу (например, в моих функциях магазина редуксов / thunks / sagas и т. Д.), Я надеялся, что смогу просто использовать, window.customHistory.push()но по какой-то причине react-routerникогда не обновлялся, хотя URL-адрес изменился. Но так у меня есть экземпляр EXACT react-router. Я не люблю помещать вещи в глобальную сферу, и это одна из немногих вещей, с которыми я бы сделал это. Но это лучше, чем любая другая альтернатива, которую я видел в ИМО.

Joao
источник
3

Используйте Обратный звонок. Это сработало для меня!

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

В компоненте вам просто нужно добавить обратный вызов

this.props.addProduct(props, () => this.props.history.push('/cart'))
Приянш Растоги
источник
3

так что я делаю это так: - вместо перенаправления с помощью history.pushя просто использую Redirectкомпонент из react-router-dom При использовании этого компонента вы можете просто передать push=true, и он позаботится обо всем остальном

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
источник
это правильно, что не нарушает цикл реакции рендеринга
jzqa
1

React router V4 теперь позволяет использовать историю, как показано ниже:

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

В этом случае к значению можно получить доступ везде, где доступна информация о местонахождении как state:{value}состояние компонента.

Snivio
источник
1
Это не отвечает на вопрос о том, как получить доступ к history.push ВНЕ компонента. Использование this.props.history не допускается для использования вне компонента.
Аркадий
0

Вы можете использовать это так, как я делаю это для входа в систему и разные вещи Мэнни

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
источник
0
/*Step 1*/
myFunction(){  this.props.history.push("/home"); }
/**/
 <button onClick={()=>this.myFunction()} className={'btn btn-primary'}>Go 
 Home</button>
Давид Багдасарян
источник
Нет необходимости в импорте!
Давид Багдасарян
1
Хотя этот код может ответить на вопрос, предоставление дополнительного контекста относительно того, почему и / или как этот код отвечает на вопрос, повышает его долгосрочную ценность.
Rollstuhlfahrer
0

Шаг первый обернуть ваше приложение в маршрутизаторе

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

Теперь все мое приложение будет иметь доступ к BrowserRouter. Шаг второй: импортирую Route, а затем передаю эти реквизиты. Вероятно, в одном из ваших основных файлов.

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

Теперь, если я запускаю console.log (this.props) в моем js-файле компонента, я должен получить что-то похожее на это

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

Шаг 2 Я могу получить доступ к объекту истории, чтобы изменить свое местоположение

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

Кроме того, я просто студент-программист, поэтому я не эксперт, но я знаю, что вы также можете использовать

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

Поправьте меня, если я ошибаюсь, но когда я проверил это, он перезагрузил всю страницу, которая, как мне показалось, побеждала весь смысл использования React.


источник
0

Создайте кастом Routerсо своим собственным 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

Далее, в вашем Root, где вы определяете свой Router, используйте следующее:

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

Наконец, импортируйте historyтуда , где вам это нужно, и используйте его:

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

export function logout(){
  clearTokens();      
  history.push('/login'); //WORKS AS EXPECTED!
  return Promise.reject('Refresh token has expired');
}
Nolesh
источник
0

Если вы хотите использовать историю во время передачи функции в качестве значения для свойства Компонента , с помощью реакции-маршрутизатора 4 вы можете просто деструктурировать структуру historyв атрибуте рендеринга <Route/>Компонента, а затем использоватьhistory.push()

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

Примечание: чтобы это работало, вы должны обернуть компонент BrowserRouter React Router вокруг корневого компонента (например, который может быть в index.js)

Кевал Шах
источник