Est-il possible d'appeler une action redux asynchrone appelée thunk
sur une route particulière et de ne pas effectuer la transition tant que la réponse n'a pas réussi ou échoué?
Cas d'utilisation
Nous devons charger les données du serveur et remplir un formulaire avec les valeurs initiales. Ces valeurs initiales n'existent pas tant que les données ne sont pas extraites du serveur.
une syntaxe comme celle-ci serait géniale:
<Route path="/myForm" component={App} async={dispatch(loadInitialFormValues(formId))}>
Pour répondre à la question initiale d'empêcher la transition vers un nouvel itinéraire jusqu'à ce qu'une réponse ait réussi ou échoué:
Parce que vous utilisez redux thunk, le succès ou l'échec du créateur d'action peut déclencher la redirection. Je ne sais pas à quoi ressemble votre créateur d'action/action spécifique, mais quelque chose comme cela pourrait fonctionner:
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')
});
}
}
Suivre. Modèle commun de chargement de données lors de la saisie d'un itinéraire/sur le composantWillMount d'un composant et l'affichage d'un compteur:
À partir des documents Redux sur les actions asynchrones http://redux.js.org/docs/advanced/AsyncActions.html
- Une action informant les réducteurs que la demande a commencé.
Les réducteurs peuvent gérer cette action en activant un indicateur isFetching dans l'état. De cette façon, l’UI sait qu’il est temps de montrer un spinner.
- Une action informant les réducteurs que la demande s'est terminée avec succès.
Les réducteurs peuvent gérer cette action en fusionnant les nouvelles données dans le fichier ils gèrent et réinitialisent isFetching. L’UI cacherait le spinner et afficher les données récupérées.
- Une action informant les réducteurs que la demande a échoué.
Les réducteurs peuvent gérer cette action en réinitialisant isFetching . En outre, certains réducteurs peuvent vouloir stocker le message d'erreur afin que le L'interface utilisateur peut l'afficher.
J'ai suivi ce schéma général ci-dessous en utilisant votre situation comme une directive approximative. Vous n'êtes pas obligé d'utiliser des promesses
// 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)
Tout d’abord, je tiens à dire qu’il ya { un débat autour de} le sujet de la récupération de données avec les hooks onEnter
de react-router, qu’il s’agisse ou non d’une bonne pratique, néanmoins aller:
Vous pouvez passer le redux-store à votre Router
. Laissez ce qui suit être votre composant racine, où Router
est monté:
...
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>
);
}
}
...
Et vos itinéraires seront quelque chose comme:
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>
);
};
Veuillez noter que votre fichier de routeur exporte un thunk avec votre magasin comme argument. Si vous regardez en haut, voyez comment nous avons appelé le routeur, nous lui passons l'objet magasin.
Malheureusement, au moment de la rédaction de ce document, docs react-router me renvoie 404; je ne peux donc pas vous indiquer les documents dans lesquels (nextState, transition, callback)
sont décrits. Mais à propos de ceux-ci, de ma mémoire:
nextState
décrit la route que react-router
passera à;
transition
fonction à préformer peut être une autre transition que celle de nextState
;
callback
déclenchera la transition de votre itinéraire.
Une autre idée à souligner est qu’avec redux-thunk, votre action d’envoi peut renvoyer une promesse, vérifiez-la dans la documentation ici . Vous pouvez trouver ici un bon exemple de la configuration de votre magasin Redux avec Redux-Thunk.
maintenant browserHistory
ne fonctionne pas à cause de cette erreur:
'browserHistory' n'est pas exporté à partir de 'react-router'.
utilisez ce code à la place:
import { createHashHistory } from 'history'
const history = createHashHistory()
et utilise
this.props.history.Push('/some_url')
sur votre fetch
ou n'importe où ailleurs.