I have this very simple Component, which is connected to redux state and returns {fruit, vegetables}
. Everything works fine, but let's say I have a graph inside the Component and I if receive only updated vegetable
from API the graph is being recreated each time.
Here's my component:
const Products = ({ fruit, vegetable }) =>
<div className='Products'>
<div>{vegetable.map(....logic here)}</div>
<div>{Math.random()}</div> // this is to illustrate the component is rendering every time
<Graph>Here will be a chart or a graph with fruit</Graph> //but it gets re-rendered even though there is no new fruit
</div>
const mapStateToProps = (state) => {
return {
fruit: state.data.fruit,
vegetable: state.data.vegetable,
}
}
export default connect(mapStateToProps)(Products)
It seems to me that every-time, no matter which states is updated it re-renders the whole components.
Is there a way to prevent that?
As the first argument passed in to connect , mapStateToProps is used for selecting the part of the data from the store that the connected component needs. It's frequently referred to as just mapState for short. It is called every time the store state changes.
mapStateToProps() is a function used to provide the store data to your component. On the other hand, mapDispatchToProps() is used to provide the action creators as props to your component.
The significant difference between them is that mapStateToProps passes down multiple values as props, while useSelector takes the current state as an argument, returns the required data, and stores the returned value as a single variable instead of a prop.
If your mapDispatchToProps function is declared as taking two parameters, it will be called with dispatch as the first parameter and the props passed to the connected component as the second parameter, and will be re-invoked whenever the connected component receives new props.
When a React component gets rendered, the whole tree of components below it also gets rendered - at the exception of the components which shouldComponentUpdate
hook returns false
. So in your case, if the Products
component gets rendered, it is normal that the Graph
component also does.
You have two options here:
if your Products
component does not use the fruit
prop outside of the Graph
component, you can connect directly your Graph component to the fruit
state, and use the pure
option of the connect
function to avoid re-renders when fruit
does not change
you can define the shouldComponentUpdate
hook in your Graph
component to manually skip unnecessary renders, or use a helper library to do it for you, for example the pure
helper of the recompose
library
The first option is where optimizing react/redux apps / avoiding unnecessary renders generally starts: connect your components to the store at the lowest level where it makes sense. The second option is more of an escape hatch - but still often useful.
As you mention you use stateless components, you can use a higher-order component to benefit from the shouldComponentUpdate
hook. To understand how this works, here's how a simple implementation of it could look like this:
function pure(BaseComponent, shouldUpdateFn) {
return class extends Component {
shouldComponentUpdate(nextProps) {
return shouldUpdateFn(this.props, nextProps);
}
render() {
return <BaseComponent { ...this.props } />;
}
}
}
This would give you a pure
HOC that you could reuse over your app to avoid unnecessary renders: it works by wrapping your stateless component into a new component with the desired hook. You'd use it like so, for example:
export default pure(Graph, (props, nextProps) => props.fruit !== nextProps.fruit)
Still, i highly encourage you in having a look at recompose, which has more fine-grained implementations of this, and would avoid you to reinvent the wheel.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With