8

With react-router-redux, it appears as though the only way to get routing information is through props only. Is this right?

Here's roughly what I am doing in my app right now:

<Provider store={Store}>
  <Router history={history}>
    <Route path="/" component={App}>
      <Route path="child/:id" />
    </Route>
  </Router>
</Provider>

App

const App = (props) => 
  <div className="app">
    <Header />
    <Main {...props}/>
    <Footer />
  </div>

Main

const Main = (props) => 
  <div>
    <MessageList {...props}/>
  </div>

MessageList

let MessageList = (props) => {
  const {id} = props;

  // now I can use the id from the route
 }

const mapStateToProps = (state, props) => {
  return {
    id: props.params.id
  };
};

MessageList = connect(mapStateToProps)(MessageList)

What I would like to do, is remove {...props} from all of my components, and turn MessageList into this:

let MessageList = (props) => {
  const {id} = props;

  // now I can use the id from the route
 }

const mapStateToProps = (state) => {
  return {
    id: state.router.params.id
  };
};

MessageList = connect(mapStateToProps)(MessageList)

Having to pass down props in everything feels like a big step back for how clean Redux made my application. So if passing params is correct, I'm wondering why thats preferable?

My specific case that brought this up:

I have an UserInput component that sends a message (dispatches a SEND_MESSAGE action). Depending on the current page (chat room, message feed, single message, etc) the reducer should put it in the correct spot. But, with react-redux-router, the reducer doesn't know about the route, so it can't know where to send the message.

In order to fix this I need to pass the props down, attach the id to my SEND_MESSAGE action, and now the otherwise simple UserInput is handling business logic for my application.

Jeremy
  • 1
  • 77
  • 324
  • 346
Jason D
  • 757
  • 1
  • 10
  • 19

1 Answers1

9

Rather than address your question (how to read the state), I will address your problem itself (how to dispatch different actions depending on the current route).

Make your UserInput a presentational component. Instead of dispatching inside it, let is accept onSend prop that is a callback provided by the owner component. The input would call this.props.onSend(text) without knowing anything about Redux or routes.

Then, make MessageList also a presentational component that accepts onSendMessage as a prop, and forwards it to UserInput. Again, MessageList would be unaware of routes, and would just pass it down to <UserInput onSend={this.props.onSendMessage} />.

Finally, create a couple of container components that wrap MessageList for different use cases:

ChatRoomMessageList

const mapDispatchToProps = (dispatch) => ({
  onSendMessage(text) {
    dispatch({ type: 'SEND_MESSAGE', where: 'CHAT_ROOM', text })
  }
})

const ChatRoomMessageList = connect(
  mapStateToProps,
  mapDispatchToProps
)(MessageList)

FeedMessageList

const mapDispatchToProps = (dispatch) => ({
  onSendMessage(text) {
    dispatch({ type: 'SEND_MESSAGE', where: 'FEED', text })
  }
})

const FeedMessageList = connect(
  mapStateToProps,
  mapDispatchToProps
)(MessageList)

Now you can use these container components in your route handlers directly. They will specify which action is being dispatched without leaking those details to the presentational components below. Let your route handlers take care of reading IDs and other route data, but try to avoid leaking those implementation details to the components below. It’s easier in most cases when they are driven by props.


Addressing the original question, no, you should not attempt to read the router parameters from Redux state if you use react-router-redux. From the README:

You should not read the location state directly from the Redux store. This is because React Router operates asynchronously (to handle things such as dynamically-loaded components) and your component tree may not yet be updated in sync with your Redux state. You should rely on the props passed by React Router, as they are only updated after it has processed all asynchronous code.

There are some experimental projects that do keep the whole routing state in Redux but they have other drawbacks (for example, React Router state is unserializable which is contrary to how Redux works). So I think the suggestion I wrote above should solve your use case just fine.

Dan Abramov
  • 241,321
  • 75
  • 389
  • 492
  • What if I have some nested `Container Component` which is not directly created by `Router`? So it is not possible to access the router params even if I use `withRouter`? If I can't create some nested `Container Component` it is possible to send many props from the `Router` level deep into my nested component. – aisensiy Aug 12 '16 at 06:52
  • To your comment. Yes. Move the container down to where you need it. – Adrian Lynch Nov 18 '16 at 16:33