Source:  Twitter logo

I have a simple router (started with redux-router and switched to react-router to eliminate variables).

<Router history={history}>
  <Route component={Admin} path='/admin'>
    <Route component={Pages} path='pages'/>
    <Route component={Posts} path='posts'/>
  </Route>
</Router>

Admin component is basically just {this.props.children} with some nav; it is not a connected component.

Pages component is a connected component with mapStateToProps() like so:

function mapStateToProps (state) {
  return {
    pages: state.entities.pages
  };
}

Posts is even more interesting:

function mapStateToProps (state) {
  let posts = map(state.entities.posts, post => {
    return {
      ...post,
      author: findWhere(state.entities.users, {_id: post.author})
    };
  }

  return {
    posts
  };
}

And then when I load the page or switch between Posts/Pages routes I get the following in my console.log().

// react-router navigate to /posts

Admin render()
posts: map state to props
Posts render()
posts: map state to props
Posts render()
posts: map state to props

// react-router navigate to /pages

Admin render()
pages: map state to props
Pages render()
pages: map state to props

So my question is: why is mapStateToProps being called multiple times on route changes?

Also, why does a simple map function in mapStateToProps cause it to be called a third time in the Posts container?

I am using the basic logger and crashReporter middlewares from the Redux docs and it is not reporting any state changes or crashes. If the state isn't changing why are the components rendering multiple times?

By experience with react-redux, you should not process store attributes inside mapStateToProps because connect uses shallow checking of bound store attributes to check for diff.

To check if your component needs to be updated, react-redux calls mapStateToProps and checks the first level attributes of the result. If one of them changed (=== equality check), the component will update with the new props. In your situation posts change (map transform) every time mapStateToProps gets called, so your component is updated on every store change!

Your solution would be to return only direct references of your store's attributes:

function mapStateToProps (state) {
  return {
    posts: state.entities.posts,
    users: state.entities.users
  };
}

Then in your component, you could define a function that processes your data on demand:

getPostsWithAuthor() {
  const { posts, users } = this.props;

  return map(posts, post => {
    return {
      ...post,
      author: findWhere(users, {_id: post.author})
    };
  });
}
10 users liked answer #0dislike answer #010
ngasull profile pic
ngasull

Reselect allows you to create memoized selector functions for derived state processing.

Redux's documentation explains with an example how to use it. The repo's readme have a quick example too.

1 users liked answer #1dislike answer #11
Andrey Luiz profile pic
Andrey Luiz

Copyright © 2022 QueryThreads

All content on Query Threads is licensed under the Creative Commons Attribution-ShareAlike 3.0 license (CC BY-SA 3.0).