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 1046 of the free egghead.io lessons, plus get JavaScript content delivered directly to your inbox!



Existing egghead members will not see this. Sign in.

Redux: Using withRouter() to Inject the Params into Connected Components

2:58 JavaScript lesson by

We will learn how to use withRouter() to inject params provided by React Router into connected components deep in the tree without passing them down all the way down as props.

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

We will learn how to use withRouter() to inject params provided by React Router into connected components deep in the tree without passing them down all the way down as props.

Avatar
-kaik-

Hi!

I get an error retrieving filter params on the end component (props.params is null), as if "withRouter" was not working at all (however, props have a "router" property).

I'm getting same error with cloned code from your repo :(

Avatar
-kaik-

I've seen that the issue was related with the version of react-router.

However, trying to update package.json to use "react-router": "^3.0.0", throw an error with npm install.

I've solved it by using (in package.json) the last version available: "react-router": "^3.0.0-alpha.1"

In reply to -kaik-
Avatar
Vicent Gozalbes

Hello,
is it possible to use withRouter and retrieve the filter param without using connect? f.e here is how my code looks like:

import React, { Component, PropTypes } from 'react';
import { withRouter } from 'react-router';
import { toggleTodo } from '../actions';
import TodoList from './TodoList';

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'completed':
      return todos.filter(t => t.completed);
    case 'active':
      return todos.filter(t => !t.completed);
    default:
      return todos;
  }
};

class VisibleTodoList extends Component {
  componentDidMount() {
    const { store } = this.context;
    this.unsubscribe = store.subscribe(() =>
      this.forceUpdate()
    );
  }

  componentWillUnmount() {
    this.unsubscribe();
  }

  render() {
    const { store } = this.context;
    const state = store.getState();

    // how can I access to `filter` router param?

    return (
      <TodoList
        todos={getVisibleTodos(
          state.todos,
          filter
        )}
        onTodoClick={id =>
          store.dispatch(toggleTodo(id))
        }
      />
    );
  }
}

VisibleTodoList.contextTypes = {
  store: React.PropTypes.object,
};

export default withRouter(VisibleTodoList);

Thanks!

Avatar
mobility-team

Thank you! The v4.0.0-alpha.3 should also work

In reply to -kaik-
Avatar
kcrossfitter

As indicated in the video, to use withRouter(), I have to use react-router version 3.0 or higher.

But there is a problem.
- version 3.0.0 => No problem
- version 3.0.1 => After selecting 'completed' or 'active', I cannot select 'all' again.
- version 3.0.2 => After selecting 'completed' or 'active', I cannot select 'all' again.

Is this a bug in 3.0.1 or 3.0.2. How can I solve this issue?

Avatar
Omri Mor

For those using Router v4, you will have to access the match object through the history object instead of directly. trying to access match directly from mapStateToProps would always throw undefined. See below:

const mapStateToProps = (state, history) => (
    {
        todos: getTodosByFilter(state.todos, history.match.params === undefined ? 'all' : history.match.params.filter)
    }
);```

Currently, we will read the params.filter passed by the router or inside the App component. We can access the params from there, because the router injects params prop into any Route handler component specified in the route configuration. In this case, the :filter is passed inside params.

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

However, the App component itself does not really use filter. It just passes the filter down to the VisibleTodoList, which uses it to calculate the currently visible todos. Passing the params from the top level of route handlers gets tedious, so I'm removing the filter prop. Instead, I'm going to find a way to read the current router params in the VisibleTodoList itself.

App.js

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

I will add a new import called withRouter from the react-router package. It's important that we use at least React Router 3.0 for it to work well with Redux. withRouter, it takes a React component and returns a different React component that injects the router-related props, such as params, into your component.

VisibleTodoList.js

import { withRouter } from 'react-router';

I want the params to be available inside mapStateToProps, so I need to wrap the connectresult so that the connected component gets the params as a prop.

VisibleTodoList.js

const VisibleTodoList = withRouter(connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList));

I can scroll up a little bit to the mapStateToProps definition and I can change it so that, rather than read filter directly from ownProps, it's going to read it from ownProps.params.

VisibleTodoList.js

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

Finally, I specify the fallback value, just like I used to do in the app component. To make it more compact, I'm reading the params right inside the argument definition thanks to ES6's destructuring syntax.

VisibleTodoList.js

const mapStateToProps = (state, { params }) => ({
  todos: getVisibleTodos(state.todos, parms.filter || 'all'),
});

Let's recap how we made the router params available inside the connected components' mapStateToPropsfunction. We'll read the params from the ownPropsargument, which corresponds to the props passed from the parent component. The params props specifically is passed by the component generated by withRouter call. This is how they end up in props of our connected component.

VisibleTodoList.js

const mapStateToProps = (state, { params }) => ({
  todos: getVisibleTodos(state.todos, parms.filter || 'all'),
});

withRouter passes any props through itself, so we're going to see both params, and any props passed from the app in the ownProps argument. Finally, I import withRouter from the React Router package. It only works correctly with connect since React Router 3.0, so make sure you're on the recent version.

VisibleTodoList.js

const VisibleTodoList = withRouter(connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList));

VisibleTodoList.js

import { connect } from 'react-redux';
import { withRouter } from 'react-router';

The params injected by withRouter are the same exact params that get injected by default into the route handler components. You can use both ways of getting to the params and even mix them, but withRouter is handy when you need to read the current params somewhere deep in the component tree.

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