React-Router: как дождаться асинхронного действия перед переходом маршрута
можно ли вызвать асинхронное действие redux, известное как thunk
на определенном маршруте и не выполнять переход до тех пор, пока ответ не будет успешным или неудачным?
Пример Использования
нужно загрузить данные с сервера и заполнить форму с исходными значениями. Эти начальные значения не существуют, пока данные не будут извлечены с сервера.
такой синтаксис был бы отличным:
<Route path="/myForm" component={App} async={dispatch(loadInitialFormValues(formId))}>
2 ответов
чтобы ответить на первоначальный вопрос о предотвращении перехода на новый маршрут, пока ответ не будет успешным или неудачным:
поскольку вы используете redux thunk, у вас может быть успех или неудача в создателе действия, инициирующем перенаправление. Я не знаю, как выглядит ваш конкретный создатель действия / действия, но что-то вроде этого может работать:
import { browserHistory } from 'react-router'
export function loadInitialFormValues(formId) {
return function(dispatch) {
// hit the API with some function and return a promise:
loadInitialValuesReturnPromise(formId)
.then(response => {
// If request is good update state with fetched data
dispatch({ type: UPDATE_FORM_STATE, payload: response });
// - redirect to the your form
browserHistory.push('/myForm');
})
.catch(() => {
// If request is bad...
// do whatever you want here, or redirect
browserHistory.push('/myForm')
});
}
}
последующие. Общая схема загрузки данных при входе в маршрут / on componentWillMount компонента и отображение блесны:
из документов redux по асинхронным действиям http://redux.js.org/docs/advanced/AsyncActions.html
- действие, информирующее редукторы, что запрос начался.
редукторы могут обрабатывать это действие, переключая флаг isFetching в государство. Таким образом, пользовательский интерфейс знает, что пришло время показать счетчик.
- действие информирование редукторов о том, что запрос успешно завершен.
редукторы могут обрабатывать это действие путем слияния новых данных в состояние они управляют и сброс isFetching. Интерфейс будет скрыть блесны и отображать полученные данные.
- действие, информирующее редукторы о том, что запрос не удался.
редукторы могут обрабатывать это действие путем сброса isFetching. Дополнительно, некоторые редукторы могут хотеть хранить сообщение об ошибке так UI может отображать его.
я следовал этой общей схеме ниже, используя вашу ситуацию в качестве грубого руководства. Вы не должны использовать обещания
// action creator:
export function fetchFormData(formId) {
return dispatch => {
// an action to signal the beginning of your request
// this is what eventually triggers the displaying of the spinner
dispatch({ type: FETCH_FORM_DATA_REQUEST })
// (axios is just a promise based HTTP library)
axios.get(`/formdata/${formId}`)
.then(formData => {
// on successful fetch, update your state with the new form data
// you can also turn these into their own action creators and dispatch the invoked function instead
dispatch({ type: actions.FETCH_FORM_DATA_SUCCESS, payload: formData })
})
.catch(error => {
// on error, do whatever is best for your use case
dispatch({ type: actions.FETCH_FORM_DATA_ERROR, payload: error })
})
}
}
// reducer
const INITIAL_STATE = {
formData: {},
error: {},
fetching: false
}
export default function(state = INITIAL_STATE, action) {
switch(action.type) {
case FETCH_FORM_DATA_REQUEST:
// when dispatch the 'request' action, toggle fetching to true
return Object.assign({}, state, { fetching: true })
case FETCH_FORM_DATA_SUCCESS:
return Object.assign({}, state, {
fetching: false,
formData: action.payload
})
case FETCH_FORM_DATA_ERROR:
return Object.assign({}, state, {
fetching: false,
error: action.payload
})
}
}
// route can look something like this to access the formId in the URL if you want
// I use this URL param in the component below but you can access this ID anyway you want:
<Route path="/myForm/:formId" component={SomeForm} />
// form component
class SomeForm extends Component {
componentWillMount() {
// get formId from route params
const formId = this.props.params.formId
this.props.fetchFormData(formId)
}
// in render just check if the fetching process is happening to know when to display the spinner
// this could also be abstracted out into another method and run like so: {this.showFormOrSpinner.call(this)}
render() {
return (
<div className="some-form">
{this.props.fetching ?
<img src="./assets/spinner.gif" alt="loading spinner" /> :
<FormComponent formData={this.props.formData} />
}
</div>
)
}
}
function mapStateToProps(state) {
return {
fetching: state.form.fetching,
formData: state.form.formData,
error: state.form.error
}
}
export default connect(mapStateToProps, { fetchFormData })(SomeForm)
в первую очередь, Я хочу сказать, что есть дискуссия вокруг тема извлечения данных с помощью react-router's onEnter
крючки независимо от того, является ли это хорошей практикой, тем не менее, это как-то так:
вы можете передать redux-store в свой Router
. Пусть следующим будет ваш корневой компонент, где Router
установлено:
...
import routes from 'routes-location';
class Root extends React.Component {
render() {
const { store, history } = this.props;
return (
<Provider store={store}>
<Router history={history}>
{ routes(store) }
</Router>
</Provider>
);
}
}
...
и ваши маршруты будут что-то например:
import ...
...
const fetchData = (store) => {
return (nextState, transition, callback) => {
const { dispatch, getState } = store;
const { loaded } = getState().myCoolReduxStore;
// loaded is a key from my store that I put true when data has loaded
if (!loaded) {
// no data, dispatch action to get it
dispatch(getDataAction())
.then((data) => {
callback();
})
.catch((error) => {
// maybe it failed because of 403 forbitten, we can use tranition to redirect.
// what's in state will come as props to the component `/forbitten` will mount.
transition({
pathname: '/forbitten',
state: { error: error }
});
callback();
});
} else {
// we already have the data loaded, let router continue its transition to the route
callback();
}
}
};
export default (store) => {
return (
<Route path="/" component={App}>
<Route path="myPage" name="My Page" component={MyPage} onEnter={fetchData(store)} />
<Route path="forbitten" name="403" component={PageForbitten} />
<Route path="*" name="404" component={PageNotFound} />
</Route>
);
};
обратите внимание, что ваш файл маршрутизатора экспортирует thunk с вашим магазином в качестве аргумента, если вы посмотрите вверх, посмотрите, как мы вызвали маршрутизатор, мы передаем ему объект store.
к сожалению, на момент написания статьи react - router docs верните мне 404, поэтому я не могу указать вам документы, где есть. Но, о тех, из моей памяти:
nextState
описывает маршрутreact-router
будет переход на;transition
функция для преформирования, возможно, другого перехода, чем один изnextState
;callback
вызовет ваш переход маршрута к финишу.
еще подумайте, чтобы указать, что с redux-thunk ваше действие отправки может вернуть обещание, проверьте его в docs здесь. Вы можете найти здесь хороший пример того, как настроить магазин redux с redux-thunk.