nl pkr
nl pkr

Reputation: 656

React Redux and Should Component Update Optimisation

Code:

Root Component

    <Provider store={Store}>
       <AppLayoutContainer>
           {this.props.template}
       </AppLayoutContainer>
    </Provider>

AppLayoutContainer

....some code

  return(
    <div className={`AppLayoutContainer`}>
       <Modal isOpen={isModalShown} .... />
       <div>{children}</div>
     </div>
  )

....some code

function mapStateToProps(state) {
  const { app } = state;
  return {
    isModalShown: app.isModalShown
   }
}

export default connect(mapStateToProps)(AppLayoutContainer);

Problem:

Every time when Applayoutcontainer get isModalShown from Redux, My whole app is re-rendering and it's very bad. All I need is re-render only the Modal component and don't touch my children. I think it's possible with ShouldComponentUpdate method, but I really don't understand how to do it.

Any ideas ? I know that someone did it in his project and can advise some Best Practise for this. Thanks


UPDATE:

Read all answers and YES it's good solution to connect Modal with Store, but what if

1) I want Modal component be pure (re usable) like or components ?

2) I need to use isModalShown in AppLayoutContainer ?

return(
    <div className={`AppLayoutContainer`}>
       <Modal isOpen={isModalShown} .... />
       <div>{children}</div>
     </div>
  )

UPDATE 2.0

Thank You for the Answer but what can I do with this

The main problem that I need to blur all my content when I show Modal component, as I know blur() can be applied only on content to work (not on absolute position div that overlay my content)and do it like this

return(
        <div className={`AppLayoutContainer`}>
           <Modal isOpen={isModalShown} .... />
           <div className={isModalShown ? 'blured' : null }>{children}</div>
         </div>
      )

But my whole app will re-render every time when I show up my Modal, I need to freeze it. The solution with build the container for child and do shouldcomponentupdate here isn't good, how can React compare the Whole components ?? It only compare non immutable fields in props.

Any solutions ?

Upvotes: 3

Views: 5663

Answers (3)

DDS
DDS

Reputation: 4375

What you should do is not pass the isModal in the AppLayoutContainer's mapStateToProps function, but instead connect the Modal itself.

Any results from mapStateToProps that change will cause the connected component to rerender. In your case, your mapStateToProps connected to AppLayoutContainer returns the value of isModalShown which causes your AppLayoutContainer to rerender.

If you want to only rerender the Modal itself then just

export default connect(state => ({isOpen: state.app.isModalShown}))(Modal)

in your Modal file, or in a separate file if you like that better.

Also remove the isModalShown prop from the AppLayoutContainer because you don't need it and because leaving it there means the AppLayoutContainer will still rerender every time isModalShown changes (which is what you're trying to fix).

Update regarding question updates:

Issue 1:

You can have a pure Modal

export default function Modal(props) {
  return <span>Open={props.isOpen}</span>;
}

You can then make a specific Modal for a particular use:

import Modal from './modal';
import { connect } from 'react-redux';

export default const AppLayoutModal = connect(state => ({isOpen: state.app.isModalShown}))(Modal);

You can do this many times for all kinds of separate modals. In fact this is why is a good idea to use pure components.

Issue 2:

If you want to use isModalShown in AppLayoutContainer then you have some choices:

  • Just use it. React always renders an entire component. Because it's just one render method. It updates the DOM, if any, more fine-grained but if you want less stuff to render when something changes then it's up to you to make your render functions small. If you leave the component large then the whole thing will rerender.
  • You can chop down your components into smaller ones so they can be rendered separately. You can make a small component to only use isModalShown via connect directly so the large AppLayoutContainer doesn't change. Child components can and do update independently from their parent components.

Update 2

To blur your entire app, just add a class to the app. Child components will not rerender. Redux prevents the children from rendering because it notices the children aren't using isModalShown. The resulting React element is the exact same object (a reference) as the previous render. React sees that it's the same element and knows not to update the whole tree. This works because React elements are immutable. Your whole app will not rerender. In fact, React will only add or remove the class from the class attribute.

If your children do not use Redux to prevent the render from walking up the tree, then you can wrap the children in a Redux connected component. Alternatively, you can use shouldComponentUpdate to prevent the render of the children.

Bare React asks you to send data down the tree from the root. Redux asks you to inject data into the tree right where it's needed. The latter has the benefit that nothing else needs to be rerendered. Without Redux, the entire tree would always be rerendered on every change except for when a component uses shouldComponentUpdate to stop the propagation. That, however, stops the render not just for the current component, but also for all children. Redux eliminates this because child components can have their props updated without the parent's cooperation or knowledge.

In short: use Redux everywhere for speed gains everywhere.

Upvotes: 5

Benjamin
Benjamin

Reputation: 1070

I think DDS and Anthony Dugois have the right of it, but if those are not the answers you're looking for, you could always pipe children into a new component, and use shouldComponentUpdate there.

Upvotes: 0

Anthony Dugois
Anthony Dugois

Reputation: 2052

You have different strategies possible here.

  1. Use shouldComponentUpdate in your child components (very messy and inelegant in this case) ;
  2. Connect the Modal component to the store instead of AppLayoutContainer ;
  3. Store isModalShown in the state of Modal, not in the global state.

When possible, the last solution is often the more appropriate IMHO. You keep the UI state in the related component, because you don't need very often to be aware of this in other components.

class Modal extends Component {
  state = {
    isModalShown: false,
  };

  render() {
    // ...
  }
}

In the case you need to know isModalShown in other places, you should connect the Modal component instead of AppLayoutContainer.

export default connect((state) => ({
  isModalShown: state.app.isModalShown,
}))(Modal)

Upvotes: 1

Related Questions