特定のルートでthunk
と呼ばれる非同期reduxアクションを呼び出し、応答が成功または失敗するまで遷移を実行しないことは可能ですか?
ユースケース
サーバーからデータを読み込み、フォームに初期値を入力する必要があります。これらの初期値は、データがサーバーからフェッチされるまで存在しません。
このような構文は素晴らしいでしょう:
<Route path="/myForm" component={App} async={dispatch(loadInitialFormValues(formId))}>
応答が成功または失敗するまで新しいルートへの移行を防ぐという元の質問に答えるには:
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')
});
}
}
フォローアップ。ルートの入力時/コンポーネントのcomponentWillMountでデータをロードし、スピナーを表示する一般的なパターン:
非同期アクションに関するreduxドキュメントから http://redux.js.org/docs/advanced/AsyncActions.html
- リクエストが開始されたことをレデューサーに通知するアクション。
レデューサーは、状態のisFetchingフラグを切り替えることでこのアクションを処理できます。このようにして、UIはスピナーを表示するタイミングを認識します。
- リクエストが正常に終了したことをレデューサーに通知するアクション。
リデューサーは、新しいデータを管理者が管理する状態にマージし、isFetchingをリセットすることにより、このアクションを処理できます。 UIはスピナーを非表示にし、取得したデータを表示します。
- リクエストが失敗したことをレデューサーに通知するアクション。
レデューサーは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)
まず何よりも、私は言いたい 議論はある 反応ルータの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>
);
};
ルーターファイルがストアを引数としてサンクをエクスポートしていることに注意してください。上向きに見た場合は、ルーターの呼び出し方法を確認し、ストアオブジェクトをルーターに渡します。
悲しいことに、執筆時点では react-router docs 404が返されます。つまり、(nextState, transition, callback)
について説明しています。しかし、それらについて、私の記憶から:
nextState
はルートを説明しますreact-router
に移行します。
transition
からの遷移とは別の遷移を実行するnextState
関数。
callback
は、ルートの移行をトリガーして終了します。
もう1つ指摘すべきことは、redux-thunkを使用すると、ディスパッチアクションがpromiseを返す可能性があることです。ドキュメント here で確認してください。 here redux-thunkを使用してreduxストアを構成する方法の良い例を見つけることができます。
現在、このエラーによりbrowserHistory
は機能しません:
「browserHistory」は「react-router」からエクスポートされません。
代わりにこのコードを使用してください:
import { createHashHistory } from 'history'
const history = createHashHistory()
そして使う
this.props.history.Push('/some_url')
fetch
またはその他の場所で。