React-Redux antipattern: not-so-dumb presentational components
This is something that happened to me by doing an application with React and Redux.
This post assumes some knowledge about: Redux, Stateless vs Stateful components and Presentational vs Container components.
The Scenario
Let’s create a “chat-room” like application. So, we can have some components in mind:
- A NewMessage component (a stateful one with a form to send a message)
- A MessageList and Message component (to show messages)
- And a container for them, let’s call it Conversation for now.
I want to focus into the Message and MessageList components. These are components like:
export default ({ text }) => (
<div>{ text }</div>
);And this the MessageList
export default ({ messages }) => (
<ul>
{
messages.map((message, i) => (
<li key={i}><Message text={ message.text } />
));
}
</ul>
);The store for this application will be an array of “Message” objects. Each of them with two properties: text and status. Status can be SENDING if the message is being sent to the server, SUCCESS if the message reaches the server or FAILURE if not.
(We can add later some more sophisticated ones like READ to show that the message is read by the recipient but this is not the topic of this post)
So, this is an example of the state of the application:
[
{ text: 'Message 1', status: 'SUCCESS' },
{ text: 'Message 2', status: 'SUCCESS' },
{ text: 'Message 3', status: 'SENDING' },
{ text: 'Message 4', status: 'FAILURE' },
{ text: 'Message 5', status: 'SUCCESS' },
]
And part of the reducer:
export const SENDING = 'SENDING';
export const SUCCESS = 'SUCCESS';
export const FAILURE = 'FAILURE';
export default (messages = INITIAL_STATE, action) => {
switch (action.type) {
case SEND_MESSAGE_REQUEST:
return messages.concat({
text: action.text,
status: SENDING
});
}
};And also let’s create the container component to map Redux state to React properties.
const mapStateToProps = (messages) => messages;
...
export default connect(mapStateToProps, ...)(MessageList)
Now, we want to show the status of the message with different ways so we have created styles for them:
.statusSending {
border-bottom-color: #FF0;
}
.statusSuccess {
border-bottom-color: #0F0;
}
.statusFailure {
border-bottom-color: #F00;
}Now it’s time to modify the Message component to allow a status property:
export default ({ status, text }) => (
<div className={ status }>{ text }</div>
);That is not valid enough because status are not exactly the status names, so we need to map status values with css classes:
import { SENDING, SUCCESS, FAILURE } from 'reducers/index';
export default ({ status, text }) => {
const map = {
[SENDING]: 'statusSending',
[SUCCESS]: 'statusSuccess',
[FAILURE]: 'statusFailure',
}
}And this last thing I think is a Redux anti-pattern.
Why
Dan Abramov states what are the differences between Presentational and Container components. And if you look to the last snippet, we are importing constants of a redux reducer in a Presentational component. In other words, the Presentational component must know about the logic of the application.
If we want to maximize the usability of the component making it logic-independent, we have to do some additional steps and change the data flow:
- Limit the values of the status property of the component. The list of possible values is declared on the component and offered by it. React prop types might be helpful
- Make the Container component more smart by mapping the Redux messages’ properties to the component properties, which might not be the same.
So, let’s begin with the new Message component:
And the new Container component
What do you think? Maybe it’s too complex solution? Do you think there are better alternatives?
Thanks for reading