Reputation: 656
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);
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
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>
)
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
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 connect
ed component to rerender. In your case, your mapStateToProps
connect
ed 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:
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
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
Reputation: 2052
You have different strategies possible here.
shouldComponentUpdate
in your child components (very messy and inelegant in this case) ;AppLayoutContainer
;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