In my redux containers, I have to dispatch pretty complex actions taking a lot of properties from the store. I cannot find the right pattern to tackle the problem without crushing the performances.
Let's take the example of a container that would only contain a Send button to send a message:
(For such a small example, any of the following approach would work well, I am just trying to illustrate a problem I encounter in way bigger containers.)
Naive approach - Pass all arguments to the component
function mapStateToProps(state) {
return {
user: selectors.selectedUser(state),
title: selectors.title(state),
message: selectors.message(state),
};
}
function dispatchProps(state) {
return {
onClickSend: function(user, title, message) {
actions.sendMessage({user, title, message});
}
};
}
If I do that, my simple Send button will have to be aware of a lot of useless properties:
SendBtn.propTypes = {
user: PropTypes.string,
title: PropTypes.string,
message: PropTypes.string,
onClickSend: PropTypes.func,
}
And also to call onClickSend with all those props:
onClickSend(user, title, message)
That's way too much knowledge for the Send button. The button should only know it has to call onClickSend
when we click on it, this component is a simple button. It shouldn't know about any other props. Also, in a more complex case, it could be not just 2 props (title and message) that are needed but 5 or 10.
Another problem is performances, everytime I am going to modify the message or the title (=== on every keystroke), the send button is going to be re-rendered.
My current approach - Use mergeProps to pass arguments to the actions
The current approach of my app is currently using is relying on mergeProps:
function mapStateToProps(state) {
return {
user: selectors.selectedUser(state),
title: selectors.title(state),
message: selectors.message(state),
};
}
function mergeProps(stateProps, dispatchProps, ownProps) {
const {user, title, message} = stateProps;
const newProps = {
onClickSend: actions.sendMessage.bind(null, {
user,
title,
message
});
};
return R.mergeAll([stateProps, dispatchProps, ownProps, newProps]);
}
I find this approach way better because the Send button only has to know that it must fire its unique property: onClickSend when clicked. No need to worry about user, title or message.
However, this approach has a huge drawback: the reference of onClickSend is going to change everytime the store changes, which would lead to really poor performances on larger real-life cases.
A third approach - Access state from redux-thunk
A solution to the performance issue could be to use redux-thunk and access the state directly in the action.
// Action file
var sendMessageAction = createAction("SEND_MESSAGE", function(dispatch, getState) {
// executed by redux thunk
const state = getState();
const args = {
user: selectors.selectedUser(state),
title: selectors.title(state),
message: selectors.message(state),
}
dispatch(sendMessage(args))
})
// Container file
function mapDispatchToProps(dispatch) {
onClickSend: dispatch(sendMessageAction())
}
But I don't like this approach because:
- actions would access the store - doesn't it break the unidirectional flow?
- actions would be aware of the selectors - bad separation of concern?
- actions would become complex objects
Your approach
I have worked for a while on a big redux app now and it is by far the biggest pain I have with Redux. Surprisingly, I don't find too much information about how to solve it so I'm wondering if I'm missing something elementary.
What is your approach of that problem?