question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Workarounds for mapStateToProps getting called with new route immediately before unmount

See original GitHub issue

I just put this here in case anyone else runs into it and I think something like this should be in the docs.

Anyone working with Redux should know that mapStateToProps gets called whenever the state is changed. However, it’s easy to forget that each redux-first-router action will change the state and that a components mapStateToProps will be called when arriving at a route and also again when navigating away from that route (…immediately before the current component is unmounted, causing an unnecessary re-render). This can be problematic when you have a component-per-page and expect the RFR payload and route type to have a certain value in your mapStateToProps implementation.

UPDATE: My new workaround for this is to memoize the entire return value of mapStateToProps so that a cached return value is returned whenever your navigation route changes. You could probably also use something like reselect for this, with a custom equality checker (but this is simpler).

/** 
 * Returns a memoized value, so that the given mapToProps function
 * is only called for routes that it's concerned with. A component
 * using this selector will not re-render immediately before being
 * unmounted upon navigation to a different page.
 */
function mapToPropsForPage(pageType, mapToProps) {
  let lastReturnValue;
  return function mapPropsForPage(state) {
    if (state.location.type !== pageType) {
      return lastReturnValue;
    }
    lastReturnValue = mapToProps(state);
    return lastReturnValue;
  };
}

// Example usage:
const mapStateToProps = mapToPropsForPage(
  'PAGE_XYZ',
  (state/*, props */) => {
    // This function will only run when the state.location.type === 'PAGE_XYZ'
    return {
      id: state.location.payload.id,
      otherThings: state.other.things,
    };
  },
);

NOT RECOMMENDED: Another workaround for this is to return undefined from mapStateToProps instead of memoizing the value. I created a function that only calls the target mapStateToProps when the current state.location.type matches a given value. A simplified version of it is show below.

To use it, you just wrap your own mapStateToProps with filterState, passing filterRoute('PAGE_XYZ') as the first argument. However, I don’t recommend this method anymore since it actually causes all previously mapped properties to be changed to undefined.

/**
 * Returns a function that only calls the given mapStateToProps when stateFilter returns true.
 * @param {(state)=> boolean} stateFilter
 * @param {(state: object)=> object} mapStateToProps 
 */
function filterState(stateFilter, mapStateToProps) {
  return function mapStateToPropsFilter(state) {
    if (!stateFilter(state)) {
      return undefined; // This is perfectly fine with Redux, as a way to early-exit mapStateToProps.
    }
    return mapStateToProps(state);
  }
}
/**
 * Returns a function that returns false whenever state.location.type is not equal to the page type.
 * @param {string} type
 */
function filterRoute(type) {
  return function filterRoutePageType(state) {
    if (!state) {
      return false;
    }
    const { location } = state;
    if (!location) {
      return false;
    }
    return location.type === type;
  }
}

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:2
  • Comments:10 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
wayneblosscommented, Mar 13, 2018

Just to state the obvious: This is only a problem if you structured your app like me - where you have one component per page type. The solution could also be expanded if you have one component that handles multiple page types.

However, if you have only one mapStateToProps in your whole app, which handles every type of route - then this tactic shouldn’t be necessary.

1reaction
wayneblosscommented, Mar 13, 2018

Here’s a scenario that happened to me with the undefined state:

  1. I navigate to page with a route path of /:orgId/clients/ called OrgClientsPage.
  2. The OrgClientsPage.mapStateToProps expects state.location.payload.orgId to exist, which works just fine the first time its called.
  3. However, I click a link on the page to go to /settings/, which has no orgId in its payload.
  4. OrgClientsPage.mapStateToProps is then called again, right before navigating away from the page, but this time the location.state.payload.orgId doesn’t exist.

The other problem that can happen is if you navigate to another page which happens to have an /:orgId in its payload, despite it not being the same page type at all.

The only way to ensure that mapStateToProps will not run with incorrect payload and query inputs is to filter calls to it by looking at the state.location.type first.

Read more comments on GitHub >

github_iconTop Results From Across the Web

mapStateToProps called on componentWillUnmount #1360
The expected behavior would be that componentWillUnmount executes, the state updates to an empty result list and , only then, List container is ......
Read more >
React-redux connect()'s mapStateToProps being called ...
So my question is: why is mapStateToProps being called multiple times on route changes? Also, why does a simple map function in mapStateToProps...
Read more >
React Redux connect(): When and how to use it
Cover when and how to use the React Redux connect() API to create container components that are connected to the Redux state.
Read more >
Stale props and zombie children in Redux | Kai Hao
We run our mapStateToProps in the render phase so that they will always get the latest props after we call forceUpdate() in the...
Read more >
Useful React Hooks That You Can Use In Your Projects
get . When the component unmounts we then canceled the subscription by calling the cancel method of the source object. The useReducer Hook....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found