Redux, Ramda, Reselect, Recompose

Mitch Masia
hexient-labs
Published in
7 min readApr 20, 2018

A VERY functional approach to React token-based authentication.

Buckle in folks cause this is going to be a long one.

This post covers the intersection of authentication, Redux, Ramda, Reselect, and Recompose, all in the context of protecting our authenticated routes in a React app. We won’t be diving too deep into any library in particular, but we’ll discuss how to use them in tandem to run the world.

First let’s do a brief primer on each of our angles of attack.

Authentication

This will NOT be the most secure web app in the world. We will be flying by the large assumption that if we are currently storing a token in Redux, we are authenticated. If token is null, we are unauthenticated.

Redux

What is Redux? This is far too deep a rabbit hole to dig into right now; however, let’s rely on the fact that Redux is where we store all the state (data) for our React app.

Components can read from and write to our Redux store (imagine a big, shared JavaScript object) and this is where we will store our token. Our Redux store will be shaped like this:

// Describing the type of our Redux store using `flow`type ReduxStoreType = {
auth: {
token: ?string
}
}

For demonstration purposes, our Redux store will have one branch (auth) which has a token that can be a string or null.

Ramda

If you’re familiar with Underscore.js or Lodash, you’ll love Ramda even more. Ramda applies immutability and functional approaches to their implementation of these generic “toolbelt” functions.

One of the great advantages of Ramda is that their functions are automatically curried. Currying provides huge benefits in functional programming which relies heavily on the principle of partial application. Here’s an example without going too much into theory:

// Ramda exampleconst R = require('ramda')const DATA = [1, 2, 3, 4, 5]// Full application
const uncurriedExample = R.indexOf(3, DATA) // 2
// Partial application
const curriedExample = R.indexOf(3)(Data) // 2
// Or if you want to create a function beforehand// Create function to be used later
const indexOf3 = R.indexOf(3)
const anotherCurriedExample = indexOf3(DATA) // 2

It may not be obvious, especially if you come from an OOP background, but currying lays the groundwork for configurable factory functions and other functional programming constructs.

Reselect

Reselect is a library for computing derived data with memoized selectors.

What 🤔?

Skipping over memoization, Reselect pairs excellently with Redux to help you grab the exact data you want out of a branch of your redux store. This concept can be hard to understand, specifically if you haven’t dealt with the problem is solves. Here’s an example:

// Example Redux Store using `flow` typetype ReduxStoreType = {
friends: {
activeIndex: number,
data: Array<Friend>,
}
}

Now, if you just wanted to see your activeFriend = friends.data[activeIndex] many developers would access this data directly inside of a render function. This couples your component directly to the structure of your data which isn’t a maintainable practice in large applications and minimizes code reuse.

Here’s an example of this BAD practice:

// BAD EXAMPLE - GreatFriend.jsimport React, { Component } from 'react'
import { connect } from 'react-redux'
class GreatFriend extends Component {
render() {
const { activeIndex, data } = this.props
const activeFriend = data[activeFriend]
return <h1>{activeFriend.firstName}</h1>
}
}
const mapStateToProps = state => ({
friends: state.friends,
})
export default connect(mapStateToProps)(GreatFriend)

This is bad for a couple reasons. First, like we mentioned above, GreatFriend is directly responsible for finding the activeFriend. We’re directly exposing data-structure details to this component, which is not it’s direct responsibility.

Another issue here is that whenever anything inside the friends key of the Redux store changes, this component will re-render, leading to poor performance.

Now, here’s an example of how we can use Reselect to create a memoized selector to grab just our activeFriend eliminating re-renders and decoupling our component and data:

// GOOD EXAMPLE - GreatFriend.jsimport React from 'react'
import { connect } from 'react-redux'
import { createSelector } from 'reselect'
// Selectors
const friends = state => state.friends
// Base selector to get the activeIndex from friends
const getActiveIndex = createSelector(
friends,
_ => _.activeIndex
)
// Base selector to get the data from friends
const getData = createSelector(
friends,
_ => _.data
)
// Compose base selectors to get activeFriend
const getActiveFriend = createSelector(
[getData, getActiveIndex],
(data, activeIndex) => data[activeIndex]
)
// Component
const GreatFriend = props => <h1>{props.activeFriend.firstName}</h1>
const withRedux = connect(state => ({
activeFriend: getActiveFriend(state)
}))
export default withRedux(GreatFriend)

This may look convoluted now, but with a couple selector factory functions and some code organization, we achieve a very clean architecture that separates Redux, data access, and rendering.

Recompose

Recompose is another functional “toolbelt” library that abstracts common React functionality implementation details into pre-packaged higher-order components (HOCs).

HOCs are a very interesting and powerful pattern to enhance dumb components with new, reusable, composable functionality. Recompose exposes some simple and some complex HOCs for developers.

Here’s a quick example of how we can move in-component PropTypes declarations from within a component to an enhanced HOC.

// In-component PropTypesimport React, { Component } from 'react'
import PropTypes from 'prop-types'
class Example extends Component {
static propTypes = {
name: PropTypes.string.isRequired,
}
render() {
return <h1>{this.props.name}</h1>
}
}

Versus, the functional approach enabled by Recompose:

// Enhancer-based PropTypesimport React from 'react'
import PropTypes from 'prop-types'
import { compose, setPropTypes } from 'recompose'
const Example = props => <h1>{this.props.name}</h1>// Enhancers
const withPropTypes = setPropTypes({
name: PropTypes.name.isRequired,
})
export default compose(withPropTypes)(Example)

In the second example here, we’re able to write a dumb functional component (not class-based) and compose new functionality onto it.

This is a VERY simple, contrived example; however, hopefully it conveys the point. We’ll dive a little deeper in a sec.

At this point, we’ve covered core concepts with examples of the libraries we’ll be using to create a HOC that automatically protects our routes. Now onto our example.

Step 1: Writing our Redux Reducer + Selectors

Redux Reducers are how we declare the data stored in Redux.

For this contrived example, our Redux store will look exactly like this:

// Describing the type of our Redux store using `flow`type ReduxStoreType = {
auth: {
token: ?string
}
}

At Hexient Labs, we like to colocate our Redux actions, reducers, and selectors for a given feature in the same file due to natural cohesiveness and to avoid file explosions.

// auth.jsimport { createSelector } from 'reselect'
import { path } from 'ramda'
// Action types
export const ACTIONS = {
RESET: 'application/auth/RESET', // reset the auth token
SET: 'application/auth/SET', // set the auth token
}
// Initial state
export const initialState = {
token: null,
}
// Reducer
export default (reduxState = initialState, action) => {
const { payload, type } = action
switch (type) {
case ACTIONS.RESET:
return initialState
case ACTIONS.SET:
return { token: payload.token }
default:
return reduxState
}
}
/**
* Selectors
*
* This looks very different than the selectors we wrote above.
* This is because we're taking advantage of Ramda's automatic
* curried functions. The `state` param is applied automatically
* to the `auth` function.
*
* You could write the exact same thing like this:
* const auth = state => path(['auth'], state)
*
*/
const auth = path(['auth'])
/**
* We're using the same auto-currying principle here
*
*/
const getAuthToken = createSelector(
auth,
path['token'],
)
export const SELECTORS = {
getAuthToken,
}

Super clean, right? Ramda auto-currying ability continues to blow me away in how simple makes the rest of our data-access layer.

Step 2: Writing our Protection HOC

We’ll be using Recompose to write our own custom HOC we can use to enhance and automatically protect our Authenticated routes.

// protect.jsimport React from 'react'
import PropTypes from 'prop-types'
import { Redirect } from 'react-router-dom'
import { connect } from 'react-redux'
import {
branch,
compose,
renderComponent,
setPropTypes,
} from 'recompose'
import { SELECTORS } from './auth'
/**
* Protect is a HOC
* It enhances Base
* And spreads enhanced props onto Base
*
*/
const protect = Base => props => <BaseComponent {...props} />
// Enhancers/**
* Enhance the Base component with PropTypes
*
*/
const withPropTypes = setPropTypes({
token: PropTypes.string,
})
/**
* Use the branch HOC from Recompose
* If there is no token on the component's props,
* Redirect to /login route,
* Otherwise, show the Base component
*
*/
const withRedirect = branch(
ownProps => !ownProps.token,
renderComponent(() => <Redirect to="/login" />)
)
/**
* Use the connect HOC to connect the component to Redux
* Use the selector to get just the necessary token from Redux
*
*/
const withToken = connect(state => ({
token: SELECTORS.getAuthToken(state),
}))
/**
* Compose the HOCs together to enhance the Protected Base
*
*/
export default compose(
withToken,
withRedirect,
withPropTypes,
protect
)

Wowza. That was a big one, but in modular fashion we’re able to simply compose functionality together to enhance and protect any arbitrary component. With this protect HOC, we’re able to say:

If there's not a token in Redux, redirect to the /login route.

KABOOM! Now, the final step.

Step 3: Protect our Authenticated Routes

Imagine the following scenario, when a user lands on our app, they start at the /login page. Then once they authenticate, they are redirected to the /app page which can render any number of routes internal to the application.

At the top level of our app, we might have a Routes.js file:

// Routes.jsimport React from 'react'
import { Route, Router, Switch } from 'react-router-dom'
import AuthenticatedRoutes from './AuthenticatedRoutes'
import Login from './Login'
import protect from './protect'
const Routes = () => (
<Router>
<Switch>
<Route path="/app" component={protect(AuthenticatedRoutes)} />
<Route component={Login} />
</Switch>
</Router>
)
export default Routes

And with that little piece of magic, any user who tries to hit an authenticated route like /profile /friends /feed will automatically be checked for authentication and redirected to the Login page if necessary.

I know this was a long one, but we just covered a lot of information: 4 excellent functional libraries, and a traceable, debuggable authentication mechanism. Good luck and hit up the comments if you have questions!

--

--

Mitch Masia
hexient-labs

Mitch is a developer, teacher, and entrepreneur building cool things at Guaranteed Rate.