web-dev-qa-db-fra.com

Lancer des actions Redux en réponse aux transitions de route dans React Router

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 passé par des accessoires par redux avec le décorateur comme ça

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

Pour le moment, j'utilise la méthode du cycle de vie de componentWillReceiveProps pour répondre aux changements d'URL provenant de react-router puisque react-router passera de nouveaux accessoires au gestionnaire lorsque l'URL change dans this.props.params et this.props.query - le Le principal problème avec cette approche est que je lance une action dans cette méthode pour mettre à jour l'état - qui passe ensuite et transmet de nouveaux accessoires au composant qui déclenchera à nouveau la même méthode de cycle de vie - donc, fondamentalement, je crée une boucle sans fin, actuellement je suis en train de définir un variable d'état pour empêcher cela de se produire.

  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 OR puis-je avoir l'état du magasin directement connecté à l'état du composant au lieu de le transmettre via des accessoires? J'ai essayé de utilise la méthode statique willTransitionTo mais je n'y ai pas accès à this.props.dispatch.

53
deowk

D'accord, j'ai finalement trouvé une réponse sur la page github de redux, alors je la posterai ici. J'espère que cela épargnera de la douleur à quelqu'un.

@deowk Il y a deux parties à ce problème, je dirais. Le premier est que componentWillReceiveProps () n'est pas un moyen idéal pour répondre aux changements d'état - principalement parce qu'il vous oblige à penser impérativement, au lieu de réagir de manière réactive comme nous le faisons avec Redux. La solution consiste à stocker les informations actuelles de votre routeur (emplacement, paramètres, requête) dans votre magasin. Ensuite, tout votre état est au même endroit et vous pouvez vous y abonner en utilisant la même API Redux que le reste de vos données.

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)));

Maintenant, votre état de magasin sera toujours synchronisé avec l'état du routeur. Cela corrige la nécessité 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 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 quelque chose de plus compliqué, cependant, je recommande d'utiliser RxJS si vous y êtes ouvert. C'est exactement pour cela que 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 () de rx.

MODIFIER COMME SUGGÉRÉ PAR CNP

import { Observable } from 'rx'

function observableFromStore(store) {
  return Observable.create(observer =>
    store.subscribe(() => observer.onNext(store.getState()))
  )
}

Ensuite, il suffit d'utiliser des opérateurs observables pour souscrire à des changements d'état spécifiques. Voici un exemple de redirection depuis 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é en utilisant des modèles impératifs comme componentDidReceiveProps ().

36
deowk

Comme mentionné précédemment, la solution comprend deux parties:

1) Liez les informations de routage à l'état

Pour cela, il vous suffit de configurer react-router-redux . Suivez les instructions et tout ira bien.

Une fois que tout est défini, vous devez avoir un état routing, comme ceci:

state

2) Observez les changements de routage et déclenchez vos actions

Quelque part dans votre code, vous devriez avoir quelque chose comme ça maintenant:

// find this piece of code
export default function configureStore(initialState) {
    // the logic for configuring your store goes here
    let store = createStore(...);
    // we need to bind the observer to the store <<here>>
}

Ce que vous voulez faire, c'est observer les changements dans le magasin, afin que vous puissiez dispatch actions quand quelque chose change.

Comme @deowk l'a mentionné, vous pouvez utiliser rx, ou vous pouvez écrire votre propre observateur:

reduxStoreObserver.js

var currentValue;
/**
 * Observes changes in the Redux store and calls onChange when the state changes
 * @param store The Redux store
 * @param selector A function that should return what you are observing. Example: (state) => state.routing.locationBeforeTransitions;
 * @param onChange A function called when the observable state changed. Params are store, previousValue and currentValue
 */
export default function observe(store, selector, onChange) {
    if (!store) throw Error('\'store\' should be truthy');
    if (!selector) throw Error('\'selector\' should be truthy');
    store.subscribe(() => {
        let previousValue = currentValue;
        try {
            currentValue = selector(store.getState());
        }
        catch(ex) {
            // the selector could not get the value. Maybe because of a null reference. Let's assume undefined
            currentValue = undefined;
        }
        if (previousValue !== currentValue) {
            onChange(store, previousValue, currentValue);
        }
    });
}

Maintenant, tout ce que vous avez à faire est d'utiliser le reduxStoreObserver.js Que nous venons d'écrire pour observer les changements:

import observe from './reduxStoreObserver.js';

export default function configureStore(initialState) {
    // the logic for configuring your store goes here
    let store = createStore(...);

    observe(store,
        //if THIS changes, we the CALLBACK will be called
        state => state.routing.locationBeforeTransitions.search, 
        (store, previousValue, currentValue) => console.log('Some property changed from ', previousValue, 'to', currentValue)
    );
}

Le code ci-dessus rend notre fonction appelée à chaque fois que locationBeforeTransitions.search change d'état (suite à la navigation de l'utilisateur). Si vous le souhaitez, vous pouvez observer la chaîne de requête que et ainsi de suite.

Si vous souhaitez déclencher une action suite à des modifications de routage, tout ce que vous avez à faire est store.dispatch(yourAction) à l'intérieur du gestionnaire.

8
André Pena