Firing Redux actions in response to route transitions in React Router

前端 未结 2 1172
天命终不由人
天命终不由人 2020-12-22 19:08

I am using react-router and redux in my latest app and I\'m facing a couple of issues relating to state changes required based on the current url params and queries.

相关标签:
2条回答
  • 2020-12-22 19:25

    As mentioned before, the solution has two parts:

    1) Link the routing information to the state

    For that, all you have to do is to setup react-router-redux. Follow the instructions and you'll be fine.

    After everything is set, you should have a routing state, like this:

    state

    2) Observe routing changes and trigger your actions

    Somewhere in your code you should have something like this now:

    // 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>>
    }
    

    What you want to do is to observe changes in the store, so you can dispatch actions when something changes.

    As @deowk mentioned, you can use rx, or you can write your own observer:

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

    Now, all you have to do is to use the reduxStoreObserver.js we just wrote to observe changes:

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

    The above code makes our function to be called every time locationBeforeTransitions.search changes in the state (as a result of the user navigating). If you want, you can observe que query string and so forth.

    If you want to trigger an action as a result of routing changes, all you have to do is store.dispatch(yourAction) inside the handler.

    0 讨论(0)
  • 2020-12-22 19:27

    Alright I eventually found an answer on the redux's github page so will post it here. Hope it saves somebody some pain.

    @deowk There are two parts to this problem, I'd say. The first is that componentWillReceiveProps() is not an ideal way for responding to state changes — mostly because it forces you to think imperatively, instead of reactively like we do with Redux. The solution is to store your current router information (location, params, query) inside your store. Then all your state is in the same place, and you can subscribe to it using the same Redux API as the rest of your data.

    The trick is to create an action type that fires whenever the router location changes. This is easy in the upcoming 1.0 version of React Router:

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

    Now your store state will always be in sync with the router state. That fixes the need to manually react to query param changes and setState() in your component above — just use Redux's Connector.

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

    The second part of the problem is you need a way to react to Redux state changes outside of the view layer, say to fire an action in response to a route change. You can continue to use componentWillReceiveProps for simple cases like the one you describe, if you wish.

    For anything more complicated, though, I recommending using RxJS if you're open to it. This is exactly what observables are designed for — reactive data flow.

    To do this in Redux, first create an observable sequence of store states. You can do this using rx's observableFromStore().

    EDIT AS SUGGESTED BY CNP

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

    Then it's just a matter of using observable operators to subscribe to specific state changes. Here's an example of re-directing from a login page after a successful login:

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

    This implementation is much simpler than the same functionality using imperative patterns like componentDidReceiveProps().

    0 讨论(0)
提交回复
热议问题