Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Router + Redux - Dispatch an async action on route change?

I have a universal react app that's using redux and react-router.

I have several routes as follows:

/2016
/2015
/2014
/2013

etc.

Each route requires data from an API. Currently, i have the <Link> elements in the Navigation component dispatch an async action onClick, which populates the store with data from the API for that route.

For MVP, i'm just overwriting the post: {} contents in the store with the new post contents when the route changes, that way we get any new content that was on the API.

I've realise that having the action dispatchers on the <Link> buttons isn't optimal, as hitting the back button does not re-trigger the action dispatch to get the content for the previous route.

Is there a way to get React Router to trigger the dispatch action anytime a route change occurs? (Limiting it to listen to a specific set of routes would be a bonus).

I realise i should be getting the history from the store, but for now, it's easier to hit the API again by triggering an action dispatch in order to get the new content.

Cheers.

like image 202
Scotty Avatar asked May 24 '16 07:05

Scotty


People also ask

How do you transition to another route on successful async redux action?

Pass in a react-router history prop to your component, either by rendering your component inside a react-router <Route/> component or by creating a Higher Order Component using withRouter . Next, create the route you want to redirect to (I called mine to ). Third, call your redux action with both history and to .

Can I redirect from redux action?

We can redirect programmatically in the component itself or a middleware. In this guide, we are going to learn how to redirect when there's a successful async action. There are several ways to redirect a user to another route, including the history. push() method and the <Redirect /> component from react-router .

What happens when you dispatch an action redux?

The app code dispatches an action to the Redux store, like dispatch({type: 'counter/incremented'}) The store runs the reducer function again with the previous state and the current action , and saves the return value as the new state.


3 Answers

The 'lifecycle' hook onEnter and onChange has been removed in React-router 4 which makes most of the other answers to this question out-dated.

Whilst I recommend you to use your components lifecycle methods to achieve your goal, here is an answer to your question which works on React-router 4.

What works today is listen to the history change using History library created by the developers of React router themself and dispatch async actions from there.

// history.js
import createHistory from "history/createBrowserHistory"

const history = createHistory()

// Get the current location.
const location = history.location

// Listen for changes to the current location.
const unlisten = history.listen((location, action) => {
    //Do your logic here and dispatch if needed
})

export default history

Then import the history in your application

// App.js
import { Router, Route } from 'react-router-dom';
import Home from './components/Home';
import Login from './components/Login';
import history from './history';

class App extends Component {
  render() {
    return (
      <Router history={history}>
        <div>
          <Route exact path="/" component={Home} />
          <Route path="/login" component={Login} />
        </div>
      </Router>
    )
  }
}

Source: History library React router docs

like image 106
Matthew Barbara Avatar answered Nov 03 '22 01:11

Matthew Barbara


Yeah React Router has onEnter and onLeave hooks. You could build your routes to take your store instance, so you can access it in those helpers:

const createRoutes = (store) => {
  const fetchPosts = () => store.dispatch({
    types: ['FETCH_POSTS', 'FETCH_POSTS_SUCCESS', 'FETCH_POSTS_FAIL',
    url: '/posts'
  });

  return (
    <Route path="/" component={App}>
      <Route path="posts" component={PostList} onEnter={fetchPosts}/>
      <Route path="posts/:id" component={PostDetail} />
    </Route>
  )
}

A better solution is to use something like redial or redux-async-connect. This allows you to co-locate your component's data dependencies with your components, while retaining the ability to test your components without touching the network.

Edit: This applies to an old, no longer supported version of react-router.

like image 39
Steven Avatar answered Nov 03 '22 01:11

Steven


I prefer to have actions dispatched from the render prop itself:

<Route to="path" render={ props => {
  this.props.toggleInfoLayer(true);
  return <UserInfo />;
}} />

This is assuming you are using Redux's mapDispatchToProps argument.

I tried using the history change event handler as mentioned in the accepted answer, but I found it undesirable to be dispatching actions from a rogue file. One more place I had to think about, when Redux already provides plenty too many.

like image 27
papiro Avatar answered Nov 03 '22 01:11

papiro