Redux: déclencher des actions en réponse aux transitions de route dans react-router

Créé le 7 juil. 2015  ·  26Commentaires  ·  Source: reduxjs/redux

Salut les gars,

J'utilise react-router et redux dans ma dernière application et je suis confronté à quelques problèmes liés aux changements d'état requis en fonction des paramètres et des requêtes d'url actuels.

Fondamentalement, j'ai un composant qui doit mettre à jour son état chaque fois que l'URL change. L'état est transmis via les accessoires par redux avec le décorateur comme ceci

 @connect(state => ({
   campaigngroups: state.jobresults.campaigngroups,
   error: state.jobresults.error,
   loading: state.jobresults.loading
 }))

Pour le moment, j'utilise la méthode du cycle componentWillReceiveProps vie this.props.params et this.props.query - le principal problème avec cette approche est que je déclenche une action dans cette méthode pour mettre à jour l'état - qui passe ensuite et passe de nouveaux accessoires au composant qui déclenchera à nouveau la même méthode de cycle de vie - donc en gros, créer un sans fin boucle, actuellement je mets une variable d'état pour empêcher que cela se produise.

  componentWillReceiveProps(nextProps) {
    if (this.state.shouldupdate) {
      let { slug } = nextProps.params;
      let { citizenships, discipline, workright, location } = nextProps.query;
      const params = { slug, discipline, workright, location };
      let filters = this._getFilters(params);
      // set the state accroding to the filters in the url
      this._setState(params);
      // trigger the action to refill the stores
      this.actions.loadCampaignGroups(filters);
    }
  }

Existe-t-il une approche standard pour déclencher des actions basées sur des transitions de route OU puis-je avoir l'état du magasin directement connecté à l'état du composant au lieu de le transmettre via des accessoires? J'ai essayé d'utiliser la méthode statique willTransitionTo mais je n'ai pas accès à this.props.dispatch.

docs ecosystem question

Commentaire le plus utile

@deowk Il y a deux parties à ce problème, je dirais. La première est que componentWillReceiveProps() n'est pas un moyen idéal pour répondre aux changements d'état - principalement parce que cela vous oblige à penser de manière impérative , au lieu de réagir de manière

L'astuce consiste à créer un type d'action qui se déclenche chaque fois que l'emplacement du routeur change. C'est facile dans la prochaine version 1.0 de React Router:

// routeLocationDidUpdate() is an action creator
// Only call it from here, nowhere else
BrowserHistory.listen(location => dispatch(routeLocationDidUpdate(location)));

Désormais, l'état de votre magasin sera toujours synchronisé avec l'état du routeur. Cela résout le besoin de réagir manuellement aux modifications des paramètres de requête et à setState() dans votre composant ci-dessus - utilisez simplement le connecteur de Redux.

<Connector select={state => ({ filter: getFilters(store.router.params) })} />

La deuxième partie du problème est que vous avez besoin d'un moyen de réagir aux changements d'état de Redux en dehors de la couche de vue, par exemple pour déclencher une action en réponse à un changement d'itinéraire. Vous pouvez continuer à utiliser componentWillReceiveProps pour des cas simples comme celui que vous décrivez, si vous le souhaitez.

Pour tout ce qui est plus compliqué, cependant, je recommande d'utiliser RxJS si vous y êtes ouvert. C'est exactement ce pour quoi les observables sont conçus: un flux de données réactif.

Pour ce faire dans Redux, créez d'abord une séquence observable d'états de magasin. Vous pouvez le faire en utilisant observableFromStore() redux-rx.

import { observableFromStore } from 'redux-rx';
const state$ = observableFromStore(store);

Ensuite, il s'agit simplement d'utiliser des opérateurs observables pour souscrire à des changements d'état spécifiques. Voici un exemple de redirection à partir d'une page de connexion après une connexion réussie:

const didLogin$ = state$
  .distinctUntilChanged(state => !state.loggedIn && state.router.path === '/login')
  .filter(state => state.loggedIn && state.router.path === '/login');

didLogin$.subscribe({
   router.transitionTo('/success');
});

Cette implémentation est beaucoup plus simple que la même fonctionnalité utilisant des modèles impératifs comme componentDidReceiveProps() .

J'espère que ça t'as aidé!

Tous les 26 commentaires

@deowk Vous pouvez comparer this.props avec nextProps pour voir si les données pertinentes ont changé. Si cela n'a pas changé, vous n'avez pas à déclencher à nouveau l'action et vous pouvez échapper à la boucle infinie.

@johanneslumpe : Dans mon cas, campaigngroups est une collection d'objets assez volumineuse, il semble peu efficace d'utiliser une comparaison d'objets approfondie comme condition de cette manière?

@deowk si vous utilisez des données immuables, vous pouvez simplement comparer des références

@deowk Il y a deux parties à ce problème, je dirais. La première est que componentWillReceiveProps() n'est pas un moyen idéal pour répondre aux changements d'état - principalement parce que cela vous oblige à penser de manière impérative , au lieu de réagir de manière

L'astuce consiste à créer un type d'action qui se déclenche chaque fois que l'emplacement du routeur change. C'est facile dans la prochaine version 1.0 de React Router:

// routeLocationDidUpdate() is an action creator
// Only call it from here, nowhere else
BrowserHistory.listen(location => dispatch(routeLocationDidUpdate(location)));

Désormais, l'état de votre magasin sera toujours synchronisé avec l'état du routeur. Cela résout le besoin de réagir manuellement aux modifications des paramètres de requête et à setState() dans votre composant ci-dessus - utilisez simplement le connecteur de Redux.

<Connector select={state => ({ filter: getFilters(store.router.params) })} />

La deuxième partie du problème est que vous avez besoin d'un moyen de réagir aux changements d'état de Redux en dehors de la couche de vue, par exemple pour déclencher une action en réponse à un changement d'itinéraire. Vous pouvez continuer à utiliser componentWillReceiveProps pour des cas simples comme celui que vous décrivez, si vous le souhaitez.

Pour tout ce qui est plus compliqué, cependant, je recommande d'utiliser RxJS si vous y êtes ouvert. C'est exactement ce pour quoi les observables sont conçus: un flux de données réactif.

Pour ce faire dans Redux, créez d'abord une séquence observable d'états de magasin. Vous pouvez le faire en utilisant observableFromStore() redux-rx.

import { observableFromStore } from 'redux-rx';
const state$ = observableFromStore(store);

Ensuite, il s'agit simplement d'utiliser des opérateurs observables pour souscrire à des changements d'état spécifiques. Voici un exemple de redirection à partir d'une page de connexion après une connexion réussie:

const didLogin$ = state$
  .distinctUntilChanged(state => !state.loggedIn && state.router.path === '/login')
  .filter(state => state.loggedIn && state.router.path === '/login');

didLogin$.subscribe({
   router.transitionTo('/success');
});

Cette implémentation est beaucoup plus simple que la même fonctionnalité utilisant des modèles impératifs comme componentDidReceiveProps() .

J'espère que ça t'as aidé!

Nous en avons besoin dans de nouveaux documents. Tellement bien écrit.

@acdlite : Merci beaucoup, votre conseil m'a vraiment beaucoup aidé, j'ai du mal avec ce qui suit -> changer l'état dans le magasin en déclenchant un créateur d'action en réponse à un changement d'URL.

Je veux déclencher le créateur d'action dans la méthode statique willTransitionTo car cela semble être la seule option dans react-router v0.13.3

class Results extends Component  {
..........
}

Results.willTransitionTo = function (transition, params, query) {
 // how do I get a reference to dispatch here?
};

@deowk Je suppose que vous appelez dispatch directement sur l'instance redux:

const redux = createRedux(stores);
BrowserHistory.listen(location => redux.dispatch(routeLocationDidUpdate(location)));

@deowk J'envoie actuellement mes modifications d'url dans le routeur de réaction 0.13.3 comme ceci:

Router.run(routes, Router.HistoryLocation, function(Handler, locationState) {
   dispatch(routeLocationDidUpdate(locationState))
   React.render(<Handler/>, appEl);
});

@acdlite vraiment bien expliqué! : +1:
D'accord, cela devrait également figurer dans la nouvelle documentation :)

Pour rappel, BrowserHistory.history() est devenu BrowserHistory.addChangeListener() .

https://github.com/rackt/react-router/blob/master/modules/BrowserHistory.js#L57

ÉDITER:

Ce qui pourrait ressembler à ceci:

history.addChangeListener(() => store.dispatch(routeLocationDidUpdate(location)));

Et qu'en est-il de l'état initial de ce réducteur, @pburtchaell?

J'ai la configuration suivante:

// client.js
class App extends Component {
  constructor(props) {
    super(props);
    this.history = new HashHistory();
  }

  render() {
    return (
      <Provider store={store}>
         {renderRoutes.bind(null, this.history)}
      </Provider>
    );
  }
}

// routes.js
export default function renderRoutes(history) {

  // When the route changes, dispatch that information to the store.
  history.addChangeListener(() => store.dispatch(routeLocationDidUpdate(location)));

  return (
    <Router history={history}>
      <Route component={myAppView}>
        <Route path="/" component={myHomeView}  />
      </Route>
    </Router>
  );
};

Cela déclenche le créateur d'action routeLocationDidUpdate() à chaque fois que l'itinéraire change et au chargement initial de l'application.

@pburtchaell pouvez-vous partager votre routeLocationDidUpdate ?

@gyzerok Bien sûr. C'est un créateur d'action.

// actions/location.js
export function routeLocationDidUpdate(location) {
  return {
    type: 'LOCATION_UPDATE',
    payload: {
      ...location,
    },
  };
}

@pburtchaell, donc dans votre exemple, je ne comprends pas vraiment où chercher les données nécessaires pour un itinéraire particulier

Voici une version plus complète de mon exemple ci-dessus:

https://github.com/acdlite/redux-react-router/blob/master/README.md#bonus -reacting-to-state-changes-with-redux-rx

Je suppose que @pburtchaell utiliserait une version asynchrone de ce code pour obtenir des données (à partir d'un appel REST, etc.). Ce dont je ne suis pas sûr, c'est quoi alors? Je suppose qu'il va ensuite dans un magasin, mais ce magasin serait-il un emplacement

<strong i="7">@connect</strong>

à dans, disons dans

<Comments />

qui serait déjà «connecté» (abonné) à un commentStore? Ou simplement ajouter enregistrer ces actions de localisation dans les commentaires

Une URL comme

/comments/123

va toujours être «couplé» au composant de commentaires, alors comment puis-je le réutiliser? Désolé si c'est stupide, très confus ici. Ce qui était un exemple solide que j'ai pu trouver.

Je suis également aux prises avec cela, en déterminant comment appeler ma méthode statique fetchData(dispatch, params) (statique, car le serveur l'appelle pour distribuer des actions FETCH asynchrones avant le rendu initial).

Puisqu'une référence à dispatch existe dans le contexte, je pense que le moyen le plus propre d'obtenir cela pour un appel côté client de fetchData est un composant de type Connector qui appelle fetchData ou un shouldFetchData , ou demandez au rappel select obtenir une référence à dispatch avec state , afin que nous puissions inspecter le courant état de store et envoyez les actions FETCH selon les besoins.

Ce dernier est plus concis mais rompt le fait que select doit rester une fonction pure. Je vais examiner le premier.

Ma solution est la suivante, bien que tout à fait adaptée à ma configuration (méthode statique fetchData(dispatch, state) qui distribue les actions async _FETCH ), elle appellera tous les rappels qui lui sont passés avec les propriétés appropriées: https: // gist.github.com/grrowl/6cca2162e468891d8128 - n'utilise pas willTransitionTo, cependant, vous ne pourrez donc pas retarder la transition des pages.

Nous devons absolument ajouter les documents! Possible dans la section "Utilisation avec react-router".

Nous aurons un moyen officiel de le faire après la version 1.0.

Super d'entendre @gaearon.

Ensuite, il s'agit simplement d'utiliser des opérateurs observables pour souscrire à des changements d'état spécifiques.

J'ai un flux observable configuré pour garder ma boutique synchronisée avec tout changement d'itinéraire, et j'ai une configuration de flux pour regarder ma boutique également. Je suis intéressé par la transition vers un nouvel itinéraire lorsqu'une valeur change dans mon magasin, en particulier lorsqu'elle passe de undefined à une chaîne valide suite à la soumission réussie d'un formulaire ailleurs dans mon application.

Les deux flux sont configurés et fonctionnent, et le magasin est en cours de mise à jour, mais je suis nouveau sur Rx et j'ai des problèmes avec la requête observable ... des pointeurs? Suis-je sur la bonne voie? À peu près sûr que cela a quelque chose à voir avec distinctUntilChanged mais c'est cuire mes nouilles pour le moment, toute aide appréciée :)

EDIT: Ack! Désolé, a répondu à ma propre question. Pseudo code ci-dessous. Faites-moi savoir si ça a l'air horrible?

const didChangeProject$ = state$
  .distinctUntilChanged(state => state.project._id)
  .filter(state => typeof state.project._id !== 'undefined');

Clôture en faveur du # 177. Lorsque nous arriverons à documenter le routage, nous devrons couvrir tous les scénarios: redirection sur changement d'état, redirection sur demande de rappel, etc.

Je sais que cela est fermé ... mais avec React 0.14 et les différentes dépendances 1.0 de la version bêta en ce moment, y a-t-il une mise à jour à cela et sinon, un tutoriel sur les nouvelles capacités de React 0.14, react-router, redux, etc. écrit? Il semble y avoir beaucoup d'entre nous qui essayons de rester à jour avec les changements à venir tout en apprenant / comprenant tout cela en même temps. Je sais que les choses évoluent (jeu de mots ... euh ... pas prévu), mais il semble que je vois des morceaux de différents exemples qui ne jouent pas bien les uns avec les autres et après plusieurs jours, je ne parviens toujours pas à faire fonctionner mon application mise à jour avec routage. Très probablement de ma faute, car j'ai encore un peu de mal à comprendre comment tout cela fonctionne, en espérant simplement qu'un tutoriel mis à jour avec les dernières informations soit bientôt disponible.

Jusqu'à ce qu'il y ait un tutoriel, n'hésitez pas à regarder examples/real-world qui a le routage. Ce n'est pas "le dernier et le meilleur" pour le moment, mais cela fonctionne bien.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

mickeyreiss-visor picture mickeyreiss-visor  ·  3Commentaires

rui-ktei picture rui-ktei  ·  3Commentaires

caojinli picture caojinli  ·  3Commentaires

amorphius picture amorphius  ·  3Commentaires

CellOcean picture CellOcean  ·  3Commentaires