Ist es möglich, eine async-Redux-Aktion, die als thunk
bezeichnet wird, auf einer bestimmten Route aufzurufen und den Übergang erst durchzuführen, wenn die Antwort erfolgreich war oder fehlgeschlagen ist?
Anwendungsfall
Wir müssen Daten vom Server laden und ein Formular mit Anfangswerten füllen. Diese Anfangswerte sind nicht vorhanden, bis die Daten vom Server abgerufen werden.
eine Syntax wie diese wäre toll:
<Route path="/myForm" component={App} async={dispatch(loadInitialFormValues(formId))}>
Um die ursprüngliche Frage zu beantworten, den Übergang zu einer neuen Route zu verhindern, bis eine Antwort erfolgreich war oder fehlgeschlagen ist:
Da Sie Redux Thunk verwenden, kann dies dazu führen, dass der Ersteller der Aktion den Erfolg oder Misserfolg der Aktion auslöst. Ich weiß nicht, wie Ihr bestimmter Aktions-/Aktionsersteller aussieht, aber so etwas könnte funktionieren:
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')
});
}
}
Nachverfolgen. Übliches Muster zum Laden von Daten bei Eingabe einer Route/auf ComponentWillMount einer Komponente und Anzeigen eines Drehfelds:
Aus den redux-Dokumenten zu asynchronen Aktionen http://redux.js.org/docs/advanced/AsyncActions.html
- Eine Aktion, die die Reduzierer darüber informiert, dass die Anfrage begonnen hat.
Die Reduzierer können diese Aktion durch Umschalten eines isFetching-Flags in .__ behandeln. der Staat. Auf diese Weise weiß die Benutzeroberfläche, dass es an der Zeit ist, einen Spinner zu zeigen.
- Eine Aktion, die die Reduzierer darüber informiert, dass die Anforderung erfolgreich abgeschlossen wurde.
Die Reduzierer können diese Aktion durchführen, indem sie die neuen Daten in die .__ einführen. Zustand, den sie verwalten und zurücksetzen isFetching. Die Benutzeroberfläche würde das .__ ausblenden. Spinner, und zeigen Sie die abgerufenen Daten an.
- Eine Aktion, die die Reduzierer darüber informiert, dass die Anforderung fehlgeschlagen ist.
Die Reduzierungen können diese Aktion durch Zurücksetzen von isFetching ..__ ausführen. Darüber hinaus möchten einige Reduzierer die Fehlermeldung speichern, so dass UI kann es anzeigen.
Ich folgte diesem allgemeinen Muster unten und griff Ihre Situation als grobe Richtlinie auf. Sie müssen keine Versprechungen verwenden
// 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)
Zuallererst möchte ich sagen, dass ist eine Debatte um das Thema des Abrufens von Daten mit onEnter
des Reaktorrouters, ob es eine gute Praxis ist oder nicht, trotzdem würde so etwas in der Art sein gehen:
Sie können den redux-store an Ihre Router
übergeben. Das Folgende sollte Ihre Root-Komponente sein, an der Router
angehängt ist:
...
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>
);
}
}
...
Und Ihre Routen werden ungefähr so sein:
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>
);
};
Bitte beachten Sie, dass Ihre Router-Datei einen Thunk mit Ihrem Store als Argument exportiert. Wenn Sie nach oben schauen, sehen Sie, wie wir den Router aufgerufen haben. Wir übergeben das Store-Objekt an ihn.
Zum Zeitpunkt des Schreibens Reaktiver-Router-Dokumente geben Sie leider 404 an mich zurück. Daher kann ich Sie nicht auf die Dokumente verweisen, in denen (nextState, transition, callback)
beschrieben ist. Aber zu denen aus meiner Erinnerung:
nextState
beschreibt die Route, zu der react-router
wechselt.
transition
Funktion zur Vorformung, möglicherweise ein anderer Übergang als der von nextState
;
callback
löst den Übergang der Route aus.
Eine weitere Überlegung ist, dass Ihre Dispatch-Aktion mit redux-thunk ein Versprechen zurückgeben kann. Überprüfen Sie dies in den Dokumenten hier . Hier ist ein gutes Beispiel dafür, wie Sie Ihren redux-Store mit redux-thunk konfigurieren.
browserHistory
funktioniert jetzt aufgrund dieses Fehlers nicht:
'browserHistory' wird nicht vom 'Reakt-Router' exportiert.
verwenden Sie stattdessen diesen Code:
import { createHashHistory } from 'history'
const history = createHashHistory()
und verwenden
this.props.history.Push('/some_url')
auf Ihrer fetch
oder wo auch immer.