As Web frameworks evolve, more layers of complexity are starting to develop — especially when it comes to managing state. As the creators of Redux put it:
“If a model can update another model, then a view can update a model, which updates another model, and this, in turn, might cause another view to update. At some point, you no longer understand what happens in your app as you have lost control over the when, why, and how of its state.”
At Hootsuite, React has become an integral part our front-end stack, and we’ve recently moved towards using Redux for managing the state of our front-end. Redux is built upon the Flux pattern. Flux emphasizes application state being abstracted into stores, and unidirectional data flow through an application. Unidirectional data flow discourages mutation of state, while encouraging individual components to subscribe to state changes from a store. This separation of concerns allows components to purely act as views — that render based the data provided.
Prior to the introduction of Redux, a significant portion of components written at Hootsuite were stateful. This meant they didn’t rely on external stores, and had to manage their own state. While these components may be easier to write (since they don’t require initializing a store and defining store actions) they carry some added complexity. Namely:
- Rendering: The components re-render whenever the state or the props change
- Coupled Concerns: The components aren’t just views — they directly host the structure and logic related to updating the state
To demonstrate the differences, I’ve implemented the same component as stateful, and stateless (with an external store). This component consists of a button and some inline text. Clicking on the button changes the color of the inline text. The state in this particular component is the color of the text.
Beginning with the stateful implementation, we define a
changeColorState function which must be bound to the component itself:
Moreover, we define a helper function (also bound to the component itself) that reads the state directly, and return results accordingly:
In totality, the stateful implementation of the component ends up as:
Although the stateful implementation isn’t long, the code is tightly coupled. The functions that manipulate and read the state are bound to the component itself. This is easily observed by the frequent use of the
this keyword, which itself can be difficult to reason about.
In comparison, the stateless implementation will store state in an external Redux store. The component itself is stateless since it will purely act as a view.
As previously mentioned, there is an overhead incurred for setting up a Redux store. We begin with defining the initial state of the store:
Moreover, we define a Redux reducer. A reducer takes in the aforementioned Redux action, and returns the new state. Note that Redux emphasizes that reducers should be pure functions — meaning an entirely new object (that is a function of the old state, and the current action) should be created:
Lastly, we use
createStore to initialize the Redux store. In order to create a store we need to pass in the reducer defined above, in conjunction to the initial state. As well, we create a function
changeColorState which couples business logic (determining which color is next), and dispatches a
CHANGE_COLOR action to the store. As per Redux’s dataflow paradigm, calling
changeColorState passes the Redux action produced by
changeColor into the
changeColorReducer which then updates the state of the store:
All together, the code used for initializing the store ends up as such:
Now that we’ve defined a store, and functions to manipulate the store, we can simplify our component code:
The above helper function
StatelessComponentWrapper is a function of the props that are passed in).
In order to connect the
StatelessComponentWrapper to the defined store, a
mapStateToProps function is used. The
mapStateToProps function defines which parts of the store the
StatelessComponentWrapper component wants to subscribe to. In this case,
StatelessComponentWrapper subscribes to any changes in
color being passed in as props. Since React components re-render whenever the provided props change, any time
color changes the
StatelessComponentWrapper component will re-render.
Although stateful components are oftentimes easier to write, stateless components offer a number of benefits including:
- Rendering: The components only re-render whenever the props change
- Separation of Concerns: The business logic related to managing state is abstracted away, leaving the components to purely act as views
- Unbounded Functions: Using the
thiskeyword is no longer necessary because we’re no longer bound to a specific instance of the component. This leads to code that is much easier to read (and consequently reason about)
Rajdeep Kambo is a co-op Student on the Plan and Create team at Hootsuite. He is currently studying Computer Engineering at The University of British Columbia.