Redux: Filtering Redux State with React Router Params

Dan Abramov
InstructorDan Abramov

Share this video with your friends

Send Tweet
Published 6 years ago
Updated 3 years ago

We will learn how adding React Router shifts the balance of responsibilities, and how the components can use both at the same time.

[00:00] I'm using the links provided by React Router now, so when I click on a link, the URL gets updated. However, the content does not get updated because the visible todo list component in its mapStateToProps function still depends on the visibility filter in the Redux store instead of reading it from the URL.

[00:20] I am adding an argument called ownProps to the mapStateToProps function, and I'm going to read the current visibility filter from ownProps. I will also change the get visible todos function to use the current convention we use for the filter prop that is all completed or active.

[00:40] The visible todo list component gets rendered from the app, so this is where we need to add the filter prop to make it available in the mapStateToProps function of the visible todo list.

[00:53] We want the filter prop to correspond to the current filter parameter in our route configuration. React Router makes such parameters available to the route handler components in a special prop called params, so I'm adding a params prop to the app, and now I can read the filter from params.filter.

[01:14] Since its empty on the root path, I'm passing all as a fallback to the visible todo list. Now I'll refresh the app, and as I press the visibility filter links, not only does the URL update, but also the console updates, even if I use back and forward buttons in the browser.

[01:36] One common problem after enabling routing is, if we go to another path and press refresh, your dev server may not be configured correctly to serve anything but the root path.

[01:48] I'm using Express with webapp-dev middleware in my development, so I just need to tell Express to serve index HTML no matter what the path is. This way, the dev server always serves the same HTML file, and React Router matches the route on the client.

[02:06] Now that the visibility filter is managed by React Router, I no longer need the visibility filter reducer. I'm just going to delete it and also remove it from the combined reducers declaration in reducers/index.js.

[02:21] Let's recap how React Router became the source of truth for the visibility filter. In the root component, I render the router, and I only have a single route with an optional filter parameter. The filter parameter gets passed into the app component by React Router. React Router will make it available inside a special params prop.

[02:46] I am passing the filter param from the URL, or all if we are on the root path, to the visible todo list component as a filter prop. The visible todo list component now reads the filter from its props and not from the state, so it uses the filter specified by the app.

[03:06] I change the get visible todos function to use the current filter names, all completed and active. For consistency, the footer component now also uses these names as the value of the filter prop for the filter link, and I re-implemented the filter link to use the link provided by React Router instead of our own implementation.

[03:30] Finally, I fix the dev server to correctly return index HTML no matter which path is requested, so that React Router can pick it up on the client.

[03:40] You might think that making the router in control of the visibility filter contradicts the idea of a single-state tree. However, in practice, what matters is that there's just one single source of truth for any independent piece of data.

[03:56] We're using Redux as the source of truth for the todos, and we're using React Router as the source of truth for anything that can be computed from the URL. In our case, this is the current visibility filter.

Darrell
Darrell
~ 5 years ago

It was distracting to suddenly hear about webpackDevMiddleware, Express, etc. with no explanation, no introduction, no links, etc. Broke the nice progression of the tutorial.

Omri Mor
Omri Mor
~ 5 years ago

For those who are using React-Router v4 in their projects the syntax for an optional param is placing a '?' after the param name. For example:

<Route path="/:filter?" component={TodoApp}/>

http://stackoverflow.com/questions/35604617/react-router-with-optional-path-parameter

Alex Okros
Alex Okros
~ 4 years ago

To pass your props from Route to App, you need to do this

<Route path='/:filter?' render={props => <App {...props} />} />

So you'd have this Root.js file:

import React from 'react';
import { Provider } from 'react-redux';
import { browserHistory } from 'react-router';
import { BrowserRouter, Route } from 'react-router-dom';
import App from './App';

const Root = ({store}) => (
    <Provider store={store}>
        <BrowserRouter history={browserHistory}>
            <Route path='/:filter?' render={props => <App {...props} />} />
        </BrowserRouter>
    </Provider>
);

export default Root;

Basically replace the component prop with the render prop. And inside the render prop, thru the arrow function, you pass the props of Route down to the App component.

But here's the buzzer.

The data tree has changed.

Inside your App component, you catch the props like this:

const App = (params) => (
    <div>
      <AddTodo />
      <VisibleTodoList filter={params.match.params.filter || 'all'} />
      <Footer />
    </div>);

So you'd have the following App.js file:

import React from 'react';
import AddTodo from './AddTodo';
import VisibleTodoList from './VisibleTodosList';
import Footer from './Footer';

const App = (params) => (
    <div>
      <AddTodo />
      <VisibleTodoList filter={params.match.params.filter || 'all'} />
      <Footer />
    </div>);

export default App;
Oleksandr Marchenko
Oleksandr Marchenko
~ 3 years ago

For the current versions

    "prop-types": "^15.7.2",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-redux": "^7.1.0",
    "react-router-dom": "^5.0.1",
    "redux": "^4.0.1"

I made it work using slightly altered Alex's answer. Changes to the App definition:

const App = ({ match }) => (
  <div>
    <AddTodo />
    <VisibleTodoList
      filter={match.params.filter || 'all'}
    />
    <Footer />
  </div>
);
App.propTypes = {
  match: PropTypes.object,
};
Roland Pangu
Roland Pangu
~ 3 years ago

Lol Express .. This was clearly before create-react-app came to existence!

Duy Nguyen
Duy Nguyen
~ 3 years ago

For those who are using React-Router v4 in their projects the syntax for an optional param is placing a '?' after the param name. For example:

<Route path="/:filter?" component={TodoApp}/>

http://stackoverflow.com/questions/35604617/react-router-with-optional-path-parameter

Huge thanks for that!