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

Dan Abramov
InstructorDan Abramov

Share this video with your friends

Send Tweet
Published 6 years ago
Updated 3 years ago

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.

[00:00] Currently, we will read the params filter passed by the router or inside the app component. We can attach 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.

[00:20] However, the app component itself does not really use filter. It just passes the filter down to the visible todo list, 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 visible todo list itself.

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

[01:09] I want the params to be available inside mapStateToProps, so I need to wrap the connect result so that the connected component gets the params as a prop. 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.

[01:34] 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 structure and syntax.

[01:51] Let's recap how we made the router params available inside the connected components' mapStateToProps function. We'll read the params from the ownProps argument, 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.

[02:16] 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 with Router from the React Router package. It only works correctly with connect since React Router 3.0so make sure you're on the recent version.

[02:40] 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 with Router it's handy when you need to read the current params somewhere deep in the component tree.

-kaik-
-kaik-
~ 6 years ago

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 :(

-kaik-
-kaik-
~ 6 years ago

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"

ysfzrn
ysfzrn
~ 6 years ago

thanks kaik

Vicent Gozalbes
Vicent Gozalbes
~ 6 years ago

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!

mobility-team
mobility-team
~ 6 years ago

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

kcrossfitter
kcrossfitter
~ 5 years ago

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?

Omri Mor
Omri Mor
~ 5 years ago

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)
    }
);```
Robert Smith
Robert Smith
~ 5 years ago

With RR4 you can also access the filter prop via match and destructure it in the function definition.

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