Search code examples
performancereactjsreduxmemoizationreselect

reselect across multiple comoponents work only with deepEqual check


I've tested in various ways... Still, It isn't working. I don't seem to doing anything wrong

  • exactly same code as reselect doc
  • redux store is all normalized
  • reducers are all immutable

From parent component, I just pass down a prop with id and from child component, connected with redux and used selector to get that exact item by id(from parent component)

### This is what Parent components render looks like
render() {
    return (
      <div>
        <h4>Parent component</h4>
        {this.props.sessionWindow.tabs.map(tabId => 
           <ChildComponentHere key={tabId} tabId={tabId} />  
        )}
      </div>
    );
  }

### This is what Child component looks like
render() {
    const { sessionTab } = this.props  (this props is from connect() )
    <div>
        <Tab key={sessionTab.id} tab={sessionTab} />
      </div>
    ))
  }
### Selectors for across multiple components
const getTheTab = (state: any, ownProps: IOwnProps) => state.sessionWindows.sessionTab[ownProps.tabId];
const makeTheTabSelector = () =>
  createSelector(
    [getTheTab],
    (tab: object) => tab
  )
export const makeMapState = () => {
  const theTabSelector = makeTheTabSelector();
  const mapStateToProps = (state: any, props: IOwnProps) => {
    return {
      sessionTab: theTabSelector(state, props)
    }
  }
  return mapStateToProps
}

Weirdly Working solution: just change to deep equality check.(from anywhere)

  • use selectors with deep equality works as expected.
  • at shouldComponentUpdate. use _.isEqual also worked.

.

 1. const createDeepEqualSelector = createSelectorCreator(
       defaultMemoize,
       isEqual
    )
 2. if (!_isEqual(this.props, nextProps) || !_isEqual(this.state, nextState)){return true}

From my understanding, my redux is always immutable so when something changed It makes new reference(object or array) that's why react re-renders. But when there is 100 items and only 1 item changed, only component with that changed props get to re-render.

To make this happen, I pass down only id(just string. shallow equality(===) works right?)using this id, get exact item.(most of the components get same valued input but few component get different valued input) Use reselect to memoize the value. when something updated and each component get new referenced input compare with memoized value and re-render when something trully changed.

This is mostly what I can think of right now... If I have to use _isEqual anyway, why would use reselect?? I'm pretty sure I'm missing something here. can anyone help?

For more clarification.(hopefully..) First,My redux data structure is like this

sessionWindow: {
  byId: {  // window datas byId
    "windowId_111": {
      id: "windowId_111",
      incognito: false,
      tabs: [1,7,3,8,45,468,35,124] // this is for the order of sessionTab datas that this window Item has
    },
    "windowId_222": {
      id: "windowId_222",
        incognito: true,
        tabs: [2, 8, 333, 111]
    },{
      ... keep same data structure as above
    }
  },
  allIds: ["windowId_222", "windowId_111"] // this is for the order of sessionWindow datas
}
sessionTab: {  // I put all tab datas here. each sessionTab doesn't know which sessionWindow they are belong to
  "1": {
    id: 1
    title: "google",
    url: "www.google.com",
    active: false,
    ...more properties
  },
  "7": {
    id: 7
    title: "github",
    url: "www.github.com",
    active: true
  },{
    ...keep same data structure as above
  }
}

Problems. 1. when a small portion of data changed, It re-renders all other components. Let's say sessionTab with id 7's url and title changed. At my sessionTab Reducer with 'SessionTabUpdated" action dispatched. This is the reducer logic

const updateSessionTab = (state, action) => {
  return {
    ...state,
    [action.tabId]: {
      ...state[action.tabId],
      title: action.payload.title,
      url: action.payload.url
    }
  }
}

Nothing is broken. just using basic reselect doesn't prevent from other components to be re-rendered. I have to use deep equality version to stop re-render the component with no data changed

After few days I've struggled, I started to think that the problem is maybe from my redux data structure? because even if I change one item from sessionTab, It will always make new reference like {...state, [changedTab'id]: {....}} In the end, I don't know...


Solution

  • Three aspects of your selector definition and usage look a little odd:

    • getTheTab is digging down through multiple levels at once
    • makeTheTabSelector has an "output selector" that just returns the value it was given, which means it's the same as getTheTab
    • In mapState, you're passing the entire props object to theTabSelector(state, props).

    I'd suggest trying this, and see what happens:

    const selectSessionWindows = state => state.sessionWindows;
    
    const selectSessionTabs = createSelector(
        [selectSessionWindows],
        sessionWindows => sessionWindows.sessionTab
    );
    
    const makeTheTabSelector = () => {
        const selectTabById = createSelector(
            [selectSessionTabs, (state, tabId) => tabId],
            (sessionTabs, tabId) => sessionTabs[tabId]
        );
        return selectTabById;
    }
    
    export const makeMapState() => {
     const theTabSelector = makeTheTabSelector();
      const mapStateToProps = (state: any, props: IOwnProps) => {
        return {
          sessionTab: theTabSelector(state, props.tabId)
        }
      }
      return mapStateToProps
    }
    

    No guarantees that will fix things, but it's worth a shot.

    You might also want to try using some devtool utilities that will tell you why a component is re-rendering. I have links to several such tools in the Devtools#Component Update Monitoring section of my Redux addons catalog.

    Hopefully that will let you figure things out. Either way, leave a comment and let me know.