Enter Your Email Address to Watch This Lesson

Your link to unlock this lesson will be sent to this email address.

Unlock this lesson and all 833 of the free egghead.io lessons, plus get JavaScript content delivered directly to your inbox!



Existing egghead members will not see this. Sign in.

Just one more step!

Check your inbox for an email from us and click link to unlock your lesson.



Redux: Filtering Redux State with React Router Params

4:09 JavaScript lesson by

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

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

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

Avatar
Michael

If we were using a selector to handle the filtering logic in getVisibleTodos, how could the value from params.filter be made accessible to the selector?

In reply to egghead.io
Avatar
Dan Abramov

We pass it to the selector from the component as the second argument.

In reply to Michael

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 VisibleTodoList component in its mapStateToProps function still depends on the visibilityFilter in the Redux store instead of reading it from the URL.

VisibleTodoList.js

const mapStateToProps = (state) => ({
  todos: getVisibleTodos(
    state.todos,
    state.visibilityFilter
  ),
});

I am adding an argument called ownProps to the mapStateToProps function, and I'm going to read the current visibilityFilter from ownProps. I will also change the getVisibleTodos function to use the current convention we use for the filter prop that is all completed or active.

VisibleTodoList.js

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'all':
      return todos;
    case 'completed':
      reutrn todos.filter(t => t.completed);
    case 'active':
      return todos.filter(t => !t.completed);
    default: 
      throw new Error('Unknown filter: ${filter}')
  } 
}

The VisibleTodoList 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 VisibleTodoList.

App.js

const App = () => (
  <div>
    <AddTodo />
    <VisibleTodoList
      filter={filter}
    />
    <Footer />
  </div>
);

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.

App.js

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

Since its empty on the root path, I'm passing all as a fallback to the VisibleTodoList. Now I'll refresh the app, and as I press the visibilityFilter links, not only does the URL update, but also the console updates, even if I use back and forward buttons in the browser.

[output](../images/javascript-redux-filtering-redux-state-with-react-router-params-output.png)

App.js

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

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.

[invalid](../images/javascript-redux-filtering-redux-state-with-react-router-params-invalid.png)

I'm using Express "See Getting Started with Express.js for more information regarding Express" with webpackDevMiddleware 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.

devServer.js

app.get('/*', (req, res) => {
  res.sendFile(path.join(__dirname, 'index.html'));
});

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

reducers/index.js

import { combineReducers } from 'redux';
import todos from './todos';

const todoApp = combineReducers({
  todos,
});

export default todoApp;

Let's recap how React Router became the source of truth for the visibilityFilter. 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.

Root.js

const Root = ({ store }) => (
  <Provider store={store}>
    <Router history={broswerHistory}>
      <Route path="/(:filter)" component={App} />
    </Router>
  </Provider>
);

App.js

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

I am passing the filter param from the URL, or 'all' if we are on the root path, to the VisibleTodoList component as a filter prop. The VisibleTodoList component now reads the filter from its props and not from the state, so it uses the filter specified by the app

VisibleTodoList.js

const mapStateToProps = (state, ownProps) => ({
  todos: getVisibleTodos(
    state.todos, 
    ownProps.filter
  ),
});

I change the getVisibleTodos 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 FilterLink, and I re-implemented the FilterLink to use the Link provided by React Router instead of our own implementation.

FilterLink.js

const FilterLink = ({ filter, children }) => {
  <Link
    to={filter === 'all' ? '' : filter}
    activeStyle={{
      textDecoration: 'none',
      color: 'black',
    }}
  >
   {children}
  </Link>
};

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.

devServer.js

app.get('/*', (req, res) => {
  res.sendFile(path.join(__dirname, 'index.html'));
});

You might think that making the router in control of the visibilityFilter 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.

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 visibilityFilter.



HEY, QUICK QUESTION!
Joel's Head
Why are we asking?