Reputation: 41
Element is using useSelector hook, it's getting value from store and basing on the value it conditionally renders . is connected to store using connect. Issue is that mapStateToProps of Child component is executed before render of Parent component.
This causes that child mapStateToProps is called even when parent will not render it with new state. I expected top to bottom update of subscribed components, including calling mapStateToProps.
I created minimal implementation to recreate this scenario https://codesandbox.io/s/red-snow-g7i0n?file=/src/index.js
const getValue = (state) => state.value;
const mapStateToPros = (state) => {
console.log("child map state to props:", state.value);
return { value: state.value };
};
class Child extends Component {
render() {
console.log("child render:", this.props.value);
return <span>{this.props.value}</span>;
}
}
const ConnectedChild = connect(mapStateToPros)(Child);
function Parent() {
const dispatch = useDispatch();
const value = useSelector(getValue);
console.log("parent render:", value);
return (
<>
<button onClick={() => dispatch({ type: "INCREASE_VALUE" })}>
action
</button>
{value < 3 ? <ConnectedChild /> : null}
</>
);
}
on console you will see
parent render: 1
child map state to props: 1
child render: 1
child map state to props: 2
parent render: 2
child render: 2
child map state to props: 3
parent render: 3
parent render: 4
Is this expected behavior, or maybe bug or corner case?
Upvotes: 2
Views: 635
Reputation: 67469
I commented on this over in Reactiflux, but I'll answer here for visibility.
There are implementation differences between connect
and useSelector
. connect
is implemented to cascade store updates in a top-down sequence, so that connected components higher in the component tree are guaranteed to have completely finished rendering before nested connected children have their mapState
s run.
However, if you don't have nested uses of connect
, then all components are going to be subscribing to the store directly. That means that all store subscriptions will run immediately and sequentially after a dispatch, regardless of where they are in the component tree, and thus before any component has a chance to re-render. So, this sounds like entirely expected behavior given the implementation.
I am curious why you're mixing useSelector
and connect
in the same component tree like this :)
For more details, see:
Upvotes: 4
Reputation: 44086
mapStateToProps
will, just like every useSelector
selector, be called on every state change because otherwise it could not make the decision if a component needs to be re-rendered. This is expected behaviour. As you see it does not directly cause any rerender of the connected component.
The order pretty much does not matter: If incoming props are equal and state is equal, the child does not have to rerender. If incoming props from a parent change, it will have to rerender no matter what (and then also take your new state & props into account).
Upvotes: 1