To the inexperienced eye, there are not many differences between a Jackson Pollock painting and a doodle. Telling what’s relevant from what is ephemeral information in ReactJS is not an easy task either.
ReactJS components let us throw as much data as we want into their state. However Flux’s philosophy recommends us that only controller-view components should manage an internal state and pass necessary data to its children through their props.
Orchestrating a Single State #
Say we have a MessageBox component that has three children components UnreadMessageCounter, MessageList and MessageComposer. We also have access to a MessageStore that will help us to fetch the necessary data about messages. The MessageBox would look something like this:
The benefit of this strategy is that if a node gets updated in the message list, we would certainly know that the event originated in the MessageBox when the state changed. This takes all the business logic complexity out of secondary components, and centralizes it into one single primary component.
But what does this has to do with application state vs UI state? Well, if we are following this philosophy about stateless secondary components we may feel tempted to throw most - if not all - our application data into the controller-view state.
This creates big challenges because we could break encapsulation, in other words higher hierarchy components will know too much about how their children work. This strives away from traditional OOP, where a given object knows nothing at all about the implementation details of the objects it depends on.
A good way to alleviate this is to make a distinction and separation between what’s application state from what’s UI state. Simple put, UI state is that kind of information that is ephemeral, exists in isolation, is not meaningful to other components, and belongs to the presentation layer only. Throwing this kind of data into the controller-view component condemns it to rot in no time.
For example, we have an EditableLabel component that renders as a label but on double click it turns into an input, then on blur it turns again into a label displaying the updated value. We will hold three of them inside a List component.
I think we would agree this code is hard to follow and understand because there are cross-references between the two components. The List knows too much about how the EditableLabel component works. It tells the EditableLabel if it’s value is being edited or not, and also dictates when to toggle between read and edit mode (see
Maybe this would be acceptable if we needed to hook other features whenever we are editing a particular label, like disabling other components until the value has been updated, but this scenario is quite unlikely to happen.
Without a solid reason behind putting all the application data and functionality in the controller-view component we shouldn’t be creating cross-references. Let’s put the presentation layer code into the EditableLabel. Keep in mind the List will still be in charge of updating the values.
I know you might be thinking this is the same mess. The numbers of lines didn’t really change, however unnecessary cross-references between the components were reduced by 50%, the List now passes two property values instead of four. Before, the List had to pass the
editing property to EditableList as well as a handler to take care of entering in edit mode.
Notice we could have left the
editing property in the List rows, while letting the EditableLabel to hold the
_enterEditMode functionality, but
editing is only a concern of the EditableLabel.
If both components were defined in separated files we wouldn’t need to switch between them repeatedly to figure out how it all works. The List now only cares about receiving a new value in order to update its state. Everything got greatly simplified by just removing the
editing property out of the List state.
To recapitulate a bit, the reasons behind moving the
editing property out of the List state are:
- It’s only important to the EditableLabel component scope.
- It’s ephemeral data, since it’s only required to toggle between edit and read mode. It’s definitely UI state.
- Keeping it inside EditableLabel greately simplifies how the whole application works, and removes noisy code out of the controller-view component.
Abusing state is bad, really bad, and we’ll be living in sin whenever we have too few or too many states laying around. It creates a lot of unnecessary complexity in both cases. Finding a good balance is the key to create more robust components.
There is still more about this topic like form validation errors. Where should an input validation error message live? What about server validation errors? I’ll try to cover all of this in a later post about forms.
If you have any question let me know in the comments section below. Thanks for reading.