React Redux accessing dynamically filtered state in mapStateToProps - rerendering woes

270 views Asked by At

I have a functional component, that is passed instructions on what to pull from the redux store. Using mapStateToProps=(state, ownProps), I can happily pull the required items from state (store) - but, at a cost of any changes in the entire state tree triggering rerunning mapStateToProps and a gazillion rerenders.

Let me unpack.

Here's a snapshot of part of the store:

{
  settings: {...stuff...},
  projects: [...stuff...],
  definitions: [...stuff...],
  themes: [...stuff...],
  surfaces: {
    '6': {                                   <--- VARIABLE PASSED TO COMPONENT
      surface: {
        STRIP: [..stuff..],
        GLOBAL: {                            <--- CATEGORY PASSED TO COMPONENT
          DISPLAY: {...stuff...},
          ASSIGNMENT: {                      <--- LIST OF REQUIRED OBJECTS HAS 
            A_TRACK: {                       SUBCATEGORY AND TARGET (A_TRACK etc...)
              value: 0,
              type: 'switch',
              label: 'TRACK'                 
            },
            A_SEND: {                        <--- ANOTHER OBJECT I NEED TO GET
              value: 0,
              type: 'switch',
              label: 'SEND'
            },
            A_PAN: {
              value: 0,
              type: 'switch',
              label: 'PAN'
            },
          },
        FADER_BANKS: {...stuff...},  
        STATUS: {...stuff...},
        LOTS_MORE_STUFF

My parent component passes the required instructions to the child.

<RefMixerGroup 
    portId = {this.props.portId}
    items={[
          {parent: 'GLOBAL', group: "ASSIGNMENT", target: "A_TRACK"},
          {parent: 'GLOBAL', group: "ASSIGNMENT", target: "A_SEND"},
           ]
          }
/>

mapStateToProps is pretty simple:

const mapStateToPropy = (state, ownProps) => {
    return {
        groupItems: getItemsFromState(state.surfaces[ownProps.portId].surface, ownProps.items)
    }
}

and the work is done in a simple function:

const getItemsFromState = (subState, items)=>{
    let groupItems=[]
    for (let i = 0; i < items.length; i++) {
        const item = items[i];
        const base = subState[item.parent];
        
        let groupItem = base[item.group][item.target]
        groupItems.push({...groupItem, target: item.target})
    }
    return groupItems
} 

But because I am creating this array of matches, I think redux thinks I should be subscribing to every item in the tree...when I only want changes on the found elements, in this case:

surfaces[6].surface[GLOBAL][ASSIGNMENT][A_TRACK]
surfaces[6].surface[GLOBAL][ASSIGNMENT][A_SEND]

I tried using reselect and the rereselect instead of my getItemsFromState function above, but all with the same result. Any change in that tree, starting with surfaces[6] triggers mapsStateToProps and a rerender.

There must be way around this, but I can't figure it out. I tried using areStatesEqual but it only provides nextState and prevState, and I need ownProps to compute equality. I possibly could use areStatePropsEqual, but that only works AFTER recomputing mapStateToProps unnecessarily.

There must be a way!

1

There are 1 answers

0
markerikson On

getItemsFromState is creating a new groupItems array reference every time it runs. It will be called after every dispatched action. Since connect re-renders any time any of the fields returned by mapState have changed to a new reference, your code is forcing React-Redux to re-render every time.

This is specifically why you should use memoized selectors to only return new derived data references if the input references have changed, typically with Reselect's createSelector. If your use of Reselect isn't helping here, it's likely that your selectors aren't being set up correctly, but I'd need to see specific examples to give advice there.

It's also why components should subscribe to the smallest amount of data that they actually need.

If you are using a function component, I'd suggest using useSelector instead of connect as well.