Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firing Redux actions in response to route transitions in React Router

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.

Basically I have a component that needs to update it's state every time the url changes. State is being passed in through props by redux with the decorator like so

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

At the moment I am using the componentWillReceiveProps lifecycle method to respond to the url changes coming from react-router since react-router will pass new props to the handler when the url changes in this.props.params and this.props.query - the main issue with this approach is that I am firing an action in this method to update the state - which then goes and passes new props the component which will trigger the same lifecycle method again - so basically creating an endless loop, currently I am setting a state variable to stop this from happening.

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

Is there a standard approach to trigger actions base on route transitions OR can I have the state of the store directly connected to the state of the component instead of passing it in through props? I have tried to use willTransitionTo static method but I don't have access to the this.props.dispatch there.

like image 594
deowk Avatar asked Jul 07 '15 12:07

deowk


People also ask

Can I use Redux with react router?

You can use the connected-react-router library (formerly known as react-router-redux ). Their Github Repo details the steps for the integration. Once the setup is complete, you can now access the router state directly within Redux as well as dispatch actions to modify the router state within Redux actions.

What are two ways of handling redirect with react router?

Two ways to handle redirecting on a user event such as create, update and delete with React Router.

How does router get active route in react?

Use the useLocation() hook to get the current route with React Router, e.g. const location = useLocation() . The hook returns the current location object. For example, you can access the pathname as location. pathname .


1 Answers

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().

like image 195
deowk Avatar answered Sep 29 '22 22:09

deowk