The Elegance Of React

Writing Elegant Code With React, Redux and Ramda

This writeup should be a lightweight introduction into writing elegant code with React. We will be combing React with Ramda to write our application in a functional style. All ideas and examples will also work with lodash/fp and other functional util libraries. The choice is yours here.

The examples have been written using eslint-config-cleanjs, to enforce a more functional style, which includes no-this and no-classes rules. They should encourage us to be more strict when we start implementing the following examples. If you’re interested in the configuration take a look at the github repository to see the exact rule sets.

Compose Components

Let’s begin with what might be the most obvious of all approaches. Think about the following:

const comp = (f, g) => x => f(g(x))

In component terms we could implement it like so f.e.:

const TodoList = (List, mapItems) => s => List(mapItems(s))

Now this makes sense, enabling us to to build larger components by composing the smaller ones.

const List = c => <ul>{c}</ul>
const Item = todo => <li key={todo.id}>{todo.name}</li>
const TodoList = compose(List, map(Item))
const result = TodoList([{id: 1, name: 'foo'}])

TodoList is a function that expects an application state and returns a new component according to this state. In itself this is very clean and sensible. We are mapping over a number of todo items and creating list items along the way. The result is then passed onto List via props and rendered inside the component.

Remember:

const App = state => List(map(Item, state)))

This first idea will work nicely for components that render other components, only that most components we write don’t depend on jsx but on a state being passed in. So this will only work for a small subset of components.

Here is a full working example using Ramda map, compose and prop.

import React from 'react'
import
{ render } from 'react-dom'
import
{ compose, map, prop } from 'ramda'
const List = items => <ul>{items}</ul>
const Item = todo => <li key={todo.id}>{todo.text}</li>
const getTodos = prop('todos')
const TodoList = compose(List, map(Item), getTodos)
const props = {todos: [{id: 1, text: 'foo'}, {id: 2, text: 'bar'}]}
render(<TodoList {...props} />, document.getElementById('root'))

Limitations of Compose and Components

We’ve been able to compose components and render a Todo list. Next, let’s see a more common approach, where props are being passed top down. These props can be anything, from callbacks, other components to arrays and objects. Here’s the same Todo List but with an additional Header component.

const Header = title => <h1>A Todo List: {title}</h1>
const List = items => <ul>{items}</ul>
const Item = todo => <li key={todo.id}>{todo.text}</li>

Nothing special here, but taking a look back at our previous implementation clearly shows that we can’t just use compose to create a Todo list containing a header and a list of Todo items. Our previous compose:

const TodoList = compose(List, map(Item), getTodos)

What we’re actually looking for is the ability to compose Header and List. Where would Header fit in? We’re passing application state to the Todo list function, which first of all filters all the todos and then iterates over the filtered todos to create an array of Items that are then passed on to List. How can Header then retrieve title information from state? We need a better approach.

Let’s be more explicit:

const TodoHeader = mapStateToProps => Header(mapStateProps)

Edit: This is a better example (Thanks Thai Pangsakulyanont)

const TodoHeader = todoState =>
Header(getTitleFromTodoState(todoState))

We want the ability to pass in application state and enable all components to retrieve any needed properties. To make it even more obvious I have called the function mapStateToProps.

const mapStateToProps = curry((f, g) => compose(g, f))

Our mapStateProps expects a function as well as a component and then applies state to the provided function first and then passes the result on to the component. Also note worthy is the fact, that we’re currying our function, just in case we want to separate the filter definition from the concrete component. On that note, most Ramda functions are automatically curried.

Here’s how mapStateToProps would be applied with Header.

const TodoHeader = mapStateToProps(s => s.title, Header)
const result = TodoHeader(state)

This is starting to sort of look like the connect() function in react-redux, we us mapStateToProps to transform any state specific information to props. Now Header and our previous composed List components can each retrieve state information individually.

const TodoList = mapStateToProps(getTodos, compose(List, map(Item))
const result = TodoList(state)

Obviously mapStateToProps solves one part of the problem, but we still need the ability to compose the results of TodoList with Header to create our App.

We can’t use compose, so let’s rollout our own handcrafted combine function, that expects two components and returns a new component back.

const combine = curry((c, o) => x => (<div>{c(x)} {o(x)}</div>))

Using combine enables to create a new function composing Header and List.

const TodoHeader = mapStateToProps(s => s.title, Header)
const TodoList = mapStateToProps(getTodos, compose(List, map(Item)))
const App = combine(TodoHeader, TodoList)
render(<App {...state} />, document.getElementById('root'))

We have a way of composing two components, each requiring a specific state to render, now. Let’s take it a step further and see how we can compose multiple components together.

Reducing Components

Imagine we need to add a Footer to our App just to be able to print out the current year. How would we achieve this? This is what might come to mind first:

const App = combine(TodoHeader, combine(TodoList, TodoFooter))

Combine TodoList with TodoFooter and then combine the resulting output with the TodoHeader. This will work, but will become obscure the more components we want to combine.

We could think about it like this:

// array of components 
const comps = [TodoHeader, TodoList, TodoFooter]
const App = comps =>
reduce((acc, x) => combine(acc, x), init, comps)

Now that we have an idea, let’s take a look at a concrete implementation.

const combineComponents = (...args) => {
const [first, ...rest] = args
return reduce((acc, c) => combine(acc, c), first, rest)
}

Referencing combineReducers in redux, we could call our reducer combineComponents. combineComponents takes a number of components and reduces them to a single function expecting the component state.

const App = combineComponents(TodoHeader, TodoList, TodoFooter)
render(<App {...state} />, document.getElementById('root'))

With the help of mapStateToProps, combine and combineComponents we are able to compose components now. Regarding mapStateToProps, we can do one last refinement. Let’s take a look at the original implementation.

const mapStateToProps = curry((f, g) => compose(g, f))

Actually we don’t even need to implement this. Ramda or lodash/fp offer a function called pipe. pipe simply run all functions from left to right. Take the following example:

const add = x => x + 1
const multiplyByFour = x => x * 4
// pipe === flip(compose)
const rCompose = flip(compose)
rCompose(add, multiplyByFour)(1) === compose(multiplyByFour, add)(1)
rCompose(add, multiplyByFour)(1) === pipe(add, multiplyByFour)(1)

So pipe is like compose with reversed arguments. We’re using the Ramda flip function, which will work in this given case, as it only flips the first two arguments. This means we can now refactor mapStateToProps to:

const mapStateToProps = pipe

or simply use pipe straight away, leveraging Ramda to the fullest. This leaves us with two functions: combine and combineReducers. We could even hide away combine, but we will leave it as it is for clarity reasons.

The full example code:

Adding Redux

Reduce everything? What follows is some pseudo code, that should help us with creating a mental model of what we’re setting out to achieve.

const App = (state, action) => TodoList

The above code looks a lot like your typical Redux reducers, only that we’re returning a React component in this specific case not a newly calculated state. What if we could leverage Redux to achieve this? Let’s try this out.

We’re still building yet another Todo list and to keep it obvious, we’ll use a couple of the redux todomvc actions and the todo reducer.

// constants
const ADD_TODO = 'ADD_TODO'
const DELETE_TODO
= 'DELETE_TODO'
// actions
const addTodo = text => ({type: ADD_TODO, text })
const deleteTodo = id => ({ type: DELETE_TODO, id })
// reducers
const todos = createReducer([], {
[ADD_TODO]: (state, action) => [
{ id: getNextId(state), completed: false, text: action.text },
...state
],
[DELETE_TODO]:(state, action) =>
reject(propEq('id', action.id), state),
})

Parts of the original reducer code have been refactored using Ramda’s reject and propEq to filter out the deleted todo item. reject is a complement of filter, in case you’re wondering. We can also write a couple of helper functions.

// redux utils
// alternative is to use
defaultTo instead propOr
const createReducer = (init, handlers) =>
(state = init, action) =>
propOr(identity, prop('type', action), handlers)(state, action)
const addOne = add(1)
const getAllIds = pluck('id')
const getMax = reduce(max, 0)
const getNextId = compose(addOne, getMax, getAllIds)

getNextId is a helper function for retrieving the next id, we will need it when adding new items. createReducer already comes as a top-level export with Redux, but this is an alternative rewritten with Ramda functions.

Now that we have the reducers and actions in place, we will need to adapt our components to handle adding and deleting components. Instead of implementing an input, we’ll keep it simple for now and add a button that handles adding a todo item with a fixed text.

const Add = onSave => (
<div>
<button onClick={() => onSave('foobar')}>Add</button>
</div>
)

Finally, we will also need a way to delete the items. Adding a delete button to Item should suffice for this example.

const Item = ({todo, removeTodo }) => (
<li key={todo.id}>
{todo.text} <button onClick={removeTodo}>Remove</button>
</li>
)

Everything should be in place now. There’s still something to clarify here: removeTodo should dispatch the deleteTodo action. Another aspect to consider is that we need to find a way to define which dispatchers should be provided. Currently we’re only mapping the state to props.

Let’s add getRender, that expects an entry node and returns a function expecting a React element.

const getRender = node => app => ReactDOM.render(app, node)
const render = getRender(document.getElementById('root'))
render(<App {...state} />)

Next off let’s also write a bindActionCreator.

// define a bindActionCreator
const bindAction = curry((dispatch, actionCreator) =>
compose(dispatch, actionCreator))
const bindActionCreator = bindAction(store.dispatch)

Let’s hide away the dispatch method, and pass the bindActionCreator to our App along with the state and subscribe to the store to trigger re-renders. Important to note that Redux already comes with a bindActionCreators helper out of the box.

const run = store.subscribe(() =>
render(
<App {...store.getState()} dispatch={bindActionCreator} />
)
)

All we need to do is some final adaptions on our Item and TodoList components. Item expects the todo item as well as an onDelete function.

const Item = ({todo, onDelete}) => (
<li key={todo.id}>
{todo.text}
<button onClick={() => onDelete(todo.id)}>Remove</button>
</li>
)

Due to the fact that Item now also requires the onDelete function we need to adapt our ‘map to props’ function. We have access to dispatch, so instead of returning the todo items array, we will return an object containing the todos as well as onDelete.

// for clearer understanding extracted mapItems
const mapItems = ({todos, onDelete}) =>
map(todo => Item({todo, onDelete}), todos)
const TodoList = pipe(props =>
({todos: props.todos, onDelete: props.dispatch(deleteTodo)}),
compose(List, mapItems)
)

Here’s the final code. Also checkout out the example.

Outro

This should have been an introduction into how to combine Ramda with React and Redux to write more elegant code.

The examples are intended to show how to use Ramda with React and/or Redux. In the real world you can leverage Ramda or lodash/fp to write more elegant code in parts of your application.

For example you might refactor your mapDispatchToProps functions to map the state to your props according to the defined propTypes instead of having to manually define them.

const getPropTypes = prop('propTypes')
const pickKeys = compose(pick, keys)
const mapStateToProps = compose(pickKeys, getPropTypes)
// map state to defined propTypes.
export default connect(mapStateToProps(App))(App)

Alternatively you could replace mapDispatchToProps with Ramda’s pick.

export default connect(pick(['todos']))(App)

If there is interest, I can do a follow-up, where we will refactor the application to integrate with Redux in a more elegant fashion.

If you have any questions or feedback don’t hesitate to leave feedback @ twitter.

This writeup has been inspired by this Brian Lonsdorf talk at React Rally.

Links

The example

Example code with redux

Example code without redux

Ramda.js

Brian Lonsdorf @ React Rally

Recompose