Limit Redux to update only components affected by the change
Asked Answered
C

2

8

trying to understand React-Redux, i find it unusual that all my components get new props when ever any slice of the state get changed. so is this by design or i'm doing something wrong ?

example App

class App extends React.Component {

  render(){return (
          <div> 
            <Navbar data={this.props.navbar} />
            <Content data={this.props.content} />
          </div>);
  }

}
select (state) => ({ navbar:state.navbar, content:state.content});
export default connect(select)(App);

Components

export const NavbarForm = props => {
  console.log('RENDERING with props--->',props);
  return (<h1>NAV {props.data.val}</h1>);
};
export const ContentForm = props => {
  console.log('RENDERING CONTENT with props--->',props);
  return (<h1>CONTENT {props.data.val}</h1>);
};

////////INDEX.js//////

const placeholderReducer = (state={val:0},action)=>{
//will update val to current time if action start with test/;
if(action.type.indexOf('TEST/') === 0)return {val:Date.now();}

return state;
}

export const rootReducer = combineReducers({
  navbar:placeholderReducer,
  content: (state,action)=>(state || {}), //**this will never do a thing.. so content should never updates right !!**
});

const store = createStore(rootReducer, {}, applyMiddleware(thunk));

render( <Provider store={store}> <App /></Provider>, document.getElementById('app')
);
setInterval(()=>{  store.dispatch(()=>{type:'TEST/BOOM'})  },3000);

okay in this app, what i expect is that Navbar component will get updated every 3000ms while content component will never updates because its reducer will always return the same state.

yet i find it really strange that both components does reRender every time an action is fired.

is this by design ? should i worry about performance if my app has 100+ component ?

Clouse answered 9/8, 2016 at 19:0 Comment(4)
You app won't scale well in terms of performance but i'll answer your main question. Turn your Navbar functional component to a class based component and add the lifecycle method componentWillReceiveProps(nextProps) then set your state... To know more about it , read herePreoccupied
@AdegbuyiAdemola why it wonnt scale ? isn't this the propsed structure from redux docs ? can you elaborate :) ? how is adding lifecycle will prevent react-redux from updating props ?Clouse
If something goes wrong with the store, you might have a maximum stack exceeded error. I didn't say adding lifecycle will prevent react-redux from.... i mean, you should use it on Navbar too and make Navbar a class-based componentPreoccupied
Remember that in React render doesn't necessarily mean change the DOM. It means run the render algorithm that will check if the DOM needs to be updated. So that render operation may be sub-millisecond. Also, check out the new React.PureComponentCommunism
R
20

This is entirely by design. React assumes that your entire app will be re-rendered from the top down by default, or at least a given subtree will be re-rendered if a certain component does a setState or something similar.

Because you only have the very top component in your app connected, everything from there on down is React's standard behavior. A parent component re-renders, causing all of its children to re-render, causing all of their children to re-render, and so on down.

The core approach to improving UI performance in React is to use the shouldComponentUpdate lifecycle method to check incoming props and return false if the component does not need to re-render. This will cause React to skip re-rendering that component and all of its descendants. Comparisons in shouldComponentUpdate are generally done using shallow reference equality, which is where the "same object references means don't update" thing becomes useful.

When using Redux and connect, you will almost always find yourself using connect on many different components in your UI. This provides a number of benefits. Components can individually extract the pieces of the store state that they need, rather than having to hand them all down from the root component. In addition, connect implements a default shouldComponentUpdate for you, and does a similar check on the values you return from your mapStateToProps function. So, in a sense, using connect on multiple components tends to give you a "free win" in regards to performance.

Further reading on the topic:

Rigatoni answered 9/8, 2016 at 19:41 Comment(0)
S
8

Yes this is by design. Action is dispatched. Reducers run. Store subscribers get notified "the store has changed". Connected components are store subscribers.

Typically you just don't worry about it until you can actually measure a performance problem that you can attribute to this - don't prematurely optimize.

If you find out that it is a problem, then you can do one of the following:

  • Add a shouldComponentUpdate method to your components so they can see that the props they received aren't different and do not need to render (there are lots of Pure Render mixins & high order components available to make this easy)
  • Instead of connecting the top-level app, connect the Navbar and Content components directly. The App will never rerender, but the children will if the store changes. And react-redux automatically uses shouldComponentUpdate to only re-render the connected components that actually have new props.
Shirker answered 9/8, 2016 at 19:39 Comment(1)
I'd suggest two more things, using the Perf tools to validate the need to optimize and using React.PureComponent from React 15.3 onwards (if applicable)Communism

© 2022 - 2024 — McMap. All rights reserved.