Seeing Through Redux with Lenses
Using functional programming to manage your state
To refresh our memories, a basic reducer
in redux is
export default (state, action) => state
Some function that takes in the previous state
object and an incoming action
object and the result of that function will be the new state
for our application.
Using this paradigm of a single reducer quickly leads to code that is hard to read but more importantly harder to update:
Each time we update a piece of state, we have to walk the whole object, copying over references to everything leading to and everything after the updated value. On top of that, we have to check to see which switch statement should be called. This reducer is just toooo smart.
Luckily for us, redux
offers combineReducers
, which lets us instead create namespaced slices of state like users
and comments
and operate on those slices of state in more isolated pieces:
combineReducers
still returns a function of signature (state, action) -> state
, but it does some plumbing for us to make sure that when we call an action that hits on the userReducer
, we give that function the slice of state called users
.
That means for every action that the reducer
decides to react to, it is responsible for returning the slice of state users
, having to copy over the entire previous state leading to the update value and all of the values after it. It still suffers from the same issues as the first, but we have just namespaced the issue. An improvement but not ideal.
If It Ain’t Broke
Using the above paradigm works and helps reduce the complexity of our state tremendously. Using the above with reselect
limits the exposure to the reducer
logic and even lets us decouple the reducer logic and result from how the UI consumes that data.
Decoupling the UI from the state
shape means that as long as the contract for the selector
is kept up to date with changes to UI needs and API responses, You could refactor one without ever affecting the other. Using selectors
, we have created a basic contract with redux
to give us a slice of shape ( or a massaging of slices of state ).
selectors
are functions that, given state
, return a piece of state. Since they are just state -> value
functions, they can literally do anything. Well, anything that isn’t helping you set that same value. All selectors
can do is select
information, not help create it.
What if there was a way to create a selector
that could not only select
or get
information from the state regardless of shape ( creating a contract between services ) but also update
or set
information regardless of shape using the same contract? What if we could decouple our reducers
in the same way we decoupled our selectors
?
Enter Lenses
In a very abstract sense, a lens
is a function that points to a specific part in a data structure. Suppose that we had the data structure:
{ user: { name: string } }
We could create a lens
that points to name
. That would mean if we view
the lens given the following object:
{ user: { name: 'Tim' } }
it would resolve into the value of 'Tim'
. Very much like a selector
where we describe a way to get a part of a shape. However, unlike selectors
, we can use lenses
to set
a value as well:
const data = { user: { name: 'Tim' } }
const updatedData = R.set(nameLens, 'John', data)
// { user: { name: 'Joh' } }
You can read more about lenses
by the amazing Randy here. For our purposes, they are just like selectors
except they allow us to set
values as well as get
them.
Your Point Is?
If we use lenses
for our selector
logic, we gain the decoupling of our reducer
and our state shape for free:
We have a file called lenses
that has a view to the root
of our state
object. We also import a function called set
from a future npm
package ( demo repo can be found here ).
Using the above syntax, these setters
allow us to describe in an a declarative way what we want to do ( set
), where we want to do it ( root
), and how we want to find that value ( with
).
How do we use lenses
to get
or select
the values? A basic example is as simple as using R.view
:
Now we can use it inside of mapStateToProps
state => ({ ids: get.account.ids(state) }) // current account.ids
What if we have a complex data structure that we need to get more than just one single lens? Our getters
are just state -> value
functions so they can be as complex as need be:
Since we know how to get values from the state
object without needing to know the shape, we can use those pieces along with the business domain mappings of the state data to our UI.
The Great Decoupling
microservices, continuous deployment, and independent services are more than just buzzwords; they are slowly becoming the de-facto standard for the web industry. And each of these things mean that our applications need to become far more decoupled from the shape of the data coming in.
Tight coupling between view and state has largely been frowned upon ( reselect
) and I believe that we should use the same metric for updating that state.
If that sounds like something you’d like to check out, here’s the repo of a dummy project that uses these ideas. It’s simple right now but feel free to fork it and show me how its done!