Using Ramda With Redux
At my job, we’ve been working on a few front-end projects using React and Redux. We’ve also been using the Ramda library to help us work effectively with Redux. This post contains a few of the ways that we’ve used Ramda in our React/Redux applications.
Background
In case you’re not already familiar with these libraries, here’s a quick overview.
React
React is “a JavaScript library for building user interfaces.” This post isn’t a React tutorial, and most of what I’m talking about here is independent of React. If you’d like to get a sense of what React is about, start with Pete Hunt’s Thinking in React article.
For this post, the main thing to know is that React emphasizes decomposing the UI into a tree of components. Each component is given “properties” (often just called props) that configure that component for a given context. A component might also have some internal state that can be mutated.
Redux
Redux is “a predictable state container for JavaScript apps.” Redux implements something like the Flux architecture using ideas from Elm. While not coupled to React, Redux is often used alongside it.
This post is also not a Redux tutorial; for that I highly recommend watching Dan Abramov’s video series on egghead.io. Even if you don’t think you’ll ever use Redux, it’s worth watching the videos to learn how to effectively teach a subject. The instructional flow of these videos is incredible.
The basic architecture of a Redux application consists of a single object containing all of the state of the application. The current state is held in a store. The UI is rendered as a pure function of the state, which makes React a great fit. The state is never modified directly; rather, user actions and/or async activities create actions which are dispatched through the store. The store feeds the current state and the action through a reducer which returns the new state of the application. The store replaces its state with the new state and updates the UI with it, completing the cycle.
There are some really nice patterns here for decomposing the UI into components, and decomposing the reducers into pieces that work on isolated parts of the state. It’s a design that lets you start small and then grow a nicely-modular architecture. I was skeptical of that at first, because the store feels like a giant global object that everything can get at. But if you follow the standard patterns, the state is only accessed in well-defined ways which keeps things from becoming a mess.
When used with React, almost all state that might be held in individual React components is moved out to the store instead. Most React components in a Redux app use only props to do their job.
The important parts of the architecture for this post are the reducer, which is defined as a pure function from (currentState, action) -> newState
, and the mapping of the state into the UI components. In Redux with React, this latter job is accomplished with a pure function mapStateToProps(state, ownProps) -> extraProps
. ownProps
are the properties that were used to instantiate the component, and extraProps
are additional properties to pass to the component along with ownProps
.
Ramda
Ramda bills itself as “a practical functional library for JavaScript programmers.” It provides many of the capabilities that functional programmers are used to. Unlike something like Immutable, it works with plain JavaScript objects.
I hadn’t done much functional programming yet, but I’m finding Ramda to be a pretty approachable way to get into it, and it really seems to fit well with the style that Redux encourages.
Let’s look at some ways we can use Ramda when writing Redux code.
Writing Reducers
There are a number of ways to use Ramda when writing reducers.
Updating Object Properties
In the Redux documentation, there’s a Todo List example. One of the reducers contains this snippet of code:
If you’re using Babel and the ES7 Object Spread notation, you can write this a bit more succinctly as:
There are a few ways to write this Ramda. Here’s a pretty direct port:
If the property to adjust is deeper in the object structure, you can use assocPath
instead.
Another way to write this is to use evolve
:
evolve
takes an object that specifies a transformation function for each key. In this case, we’re specifying that the value of the completed
property should be transformed by the not
function.
Using evolve
, you can specify multiple transformations of the state in one compact notation.
When I use evolve
, I tend to go one step further and use __
to make it a little bit nicer to read:
For a transformation this simple, I’d likely stick with the ES7 object spread syntax. But as your reducers get a bit more complicated, using Ramda can greatly simplify the code, as we’ll see in a moment.
Updating Array Elements
One of the learning apps we worked on was a simple Tic-Tac-Toe game. In this app, our state might look like this part way through the game:
We have a PLACE_TOKEN
action that includes the index at which to place the next token.
Using ES6’s array spread notation and ES7’s object spread notation, we might write the reducer for this as follows, after extracting a little nextToken
helper function:
This use of array spread is becoming a pretty common idiom for immutable updates to JavaScript arrays.
To simplify this code, we can first use Ramda’s update
function to update the board:
update
produces a new array that updates the element at index
with the provided value. If you need to transform the existing array element instead of replacing it, you can use adjust
instead.
Next we can use evolve
as outlined above to take this one step further:
In case you’re not familiar with ES6, { nextToken }
is shorthand notation for { nextToken: nextToken }
.
Note that update
takes three arguments, but we’re only providing it with two. By only providing some of the arguments, we’re getting back another function that will eventually take the remaining argument and perform the update. This is known as currying. Every function in Ramda can be curried in this way, and it becomes a pretty powerful pattern once you get used to it.
In this case, evolve
will call our curried update
function with the original value of state.board
, just like it will call our nextToken
function with the original value of state.nextToken
.
Mapping State to Props
Ramda can also be useful when mapping state to props for a component. In the Tic-Tac-Toe app, the Board
component needs both the board
and nextToken
elements of the state. The traditional way to write this is:
This can be simplified using Ramda’s pick
function:
Notice that we’re passing state
into our function, and then passing it along to pick
. When we see this pattern, it’s a clue that we can take advantage of currying. Let’s do that here:
Here again, we’ve only provided one of the two arguments that pick
needs, so we’re getting back a curried function that’s waiting for the state
to be passed in. That will happen when Redux calls the mapping function for us.
Creating Reducers
In the Reducing Boilerplate section of the Redux documentation, it suggests that it is possible to write a createReducer
function that takes an initialState and an object containing handlers, like so:
Using Ramda’s propOr
and identity
functions, we can simplify the body of the reducer function quite a bit:
propOr
takes a default value, a property name, and an object. If the object has an own-property matching the property name, the value of that property is returned. Otherwise, the default value is returned. From that, we get back a function to call on the state and action. If that function is identity
, it will just return the first parameter, which is the state. So this code is doing the exact same thing as the code above.
We can an ES6 arrow function to simplify this further:
Wrapping Up
I’m not a functional programming guru. I still couldn’t tell you what Monad is. There are parts of Ramda that are deeper than I’ve wanted to go so far. But even a few of the simpler functions can make it much easier to write Redux code.
I’m sure we’ll come up with new and interesting ways to use Ramda in Redux as we continue to work with it, but this is a good start.
If you find other ways to make use of Ramda in Redux, please share them. I haven’t seen much written about this combination, so it’d be good to have more ideas for people to explore.
Acknowledgements
Thanks to my co-worker Luke Barbuto for introducing me to Ramda. Most of these examples are things we discovered while pair-programming on several projects.