Easily Integrate Ramda into Your React Workflow
Tags: javascript, react, ramda, functional
Published: 2019-09-13 19:45:34

_Originally posted on [Hint's blog](https://hint.io/blog/react-and-ramda)._

Here at Hint, we often use React for writing our user interfaces. We enjoy its declarative API, the mental-model that makes it easier to communicate and collaborate with teams, and especially, the recent addition of hooks. React doesn't provide the entire toolkit, however. It's missing a few things out of the box: data fetching, handling async functions, applying styles in a pragmatic way, etc.

As I was learning React, the biggest hole in React's feature set actually turned out to be an issue with JavaScript itself. Compared to other toolkit heavy languages such as Ruby or Elixir, JavaScript doesn't give you a ton to work with. I started writing my own helper libraries until a friend told me about Ramda. Straight from [their homepage](https://ramdajs.com/):

> A practical functional library for JavaScript programmers.

Hey! I like functional things, libraries, JavaScript... and I'm a programmer! It was love at first byte (no, I don't feel any shame for that).

The first Ramda hurdle is functional programming. If you have never dipped a toe in the functional waters, please read [Randy Coulman's "Thinking in Ramda" series](http://randycoulman.com/blog/2016/05/24/thinking-in-ramda-getting-started/), it's brilliant.

The second Ramda hurdle (as a React developer) is knowing how to use it with React _effectively_. I'm still learning and experimenting with how the two libraries can work together, and I wanted to share some of the patterns that I have held onto over the past few years. Let's get into it!

## Make Your Code Read Better With `isNil` And `isEmpty`

Sometimes, React code isn't the easiest to read. I would argue that post-hooks this has gotten even worse. More and more logic is being added to the component's body, and without lifecycle methods that automatically help organize code out of `render`, any help I can get to cleanup, I take.

Ramda's `isNil` and `isEmpty` are a great start to make your component's body dazzle 🕺. For example:

```jsx
  const Entry = ({ client }) => (
    <Query query={currentUserQuery}>
      {({ loading, data }) => {
        if (!loading && !data.user.posts)
          return <NoPosts />

        if (data.user) {
          setErrorTrackingContext(data.user)
          getPostMetaData(data.user, client)
        }

        return (
          // code that renders things here
        )
      }}
    </Query>
  )
```

_Note on code examples: all code in this article is based on real life code that I've written. There are some references to [Apollo's React library](https://github.com/apollographql/react-apollo), which Hint loves. Most imports have been removed for brevity. No blogpost-ed, `fooBar`-filled, faux-code here. Nearly Production Ready™._

Note the first `if`: we'll return a component early if we're done loading and the `data.user.posts` is falsy. The second `if`: if we have a user, let's set the context for whatever error tracking we're using (at Hint we love [Honeybadger](https://www.honeybadger.io/)), then get some post metadata. Let's not worry about any implementations of these functions and focus on our logic. At first glance, things aren't that bad - but "not that bad" is not the bar. Excellence is! Let's take another pass, but with Ramda:

```jsx
  import { isNil, isEmpty } from 'ramda'

  const Entry = ({ client }) => (
    <Query query={currentUserQuery}>
      {({ loading, data }) => {
        if (isNil(loading) && isEmpty(data.user.posts))
          return <NoPosts />

        if (data.user) {
          setErrorTrackingContext(data.user)
          getPostMetaData(data.user, client)
        }

        return (
          // code that renders things here
        )
      }}
    </Query>
  )
```

Note the `import` at the top and the update to our first `if`. `isNil` will return `true` if `loading` is `null` or `undefined`. This function is extremely helpful because it doesn't just check if the value is `falsy`, which is essentially what it did before (`!loading`). Hindquarters saved from a nasty bug!

On the same line, `isEmpty` will return `true` if the value passed in is `''`, `[]`, or `{}`. When working with GraphQL, if you ask for a collection of things but there are none, more often than not you'll get back an empty array. Our logic check before, `!data.user.posts` could have also introduced an unintended bug! Hindquarters saved AGAIN.

### Pro-Tip

First point and already a pro-tip? Today is a good day.

Ramda is built of many tiny functions that have a single specific purpose. Assembled together properly, you can create some fun stuff! Let's create a helper that's the inverse of `isNil`:

```jsx
  import { isNil, isEmpty, complement } from 'ramda'

  const isPresent = complement(isNil)

  const Entry = ({ client }) => (
    <Query query={currentUserQuery}>
      {({ loading, data }) => {
        if (isNil(loading) && isEmpty(data.user.posts))
          return <NoPosts />

        if (isPresent(data.user)) {
          setErrorTrackingContext(data.user)
          getPostMetaData(data.user, client)
        }

        return (
          // code that renders things here
        )
      }}
    </Query>
  )
```

`complement` takes a function as its first argument, and a value as its second. If a falsy value is returned when it's called, the output will be `true` (the inverse is also true). Using `complement` makes our second `if` a little nicer.

You may say, _"Well that's really simple. Why doesn't Ramda come with a helper like that?"_ Think of Ramda functions like individual LEGOS pieces. On their own, they don't do a ton, but put them together, and you can create something incredibly useful. If you want a more _"comprehensive set of utilities"_, check out [Ramda Adjunct](https://char0n.github.io/ramda-adjunct).

## It's Dangerous to Operate on Objects Alone! Take These Functions: `prop` and `path`

_+1 internet points if you get the title joke_

As a developer, nothing is more scary than deeply accessing an object. If this doesn't make you slightly cringe:

```jsx
if (foo.bar.baz.theLastPropertyIPromise.justKiddingOneMore) doTheThing()
```

Then we need to have a talk. If this is your proposed solution:

```jsx
if (
  foo &&
  foo.bar &&
  foo.bar.baz &&
  foo.bar.baz.theLastPropertyIPromise &&
  foo.bar.baz.theLastPropertyIPromise.justKiddingOneMore
)
  doTheThing()
```

Then we _really_ need to talk.

Joking aside, we've all been there. It's easy to gloss over complex checks completely or write conditionals that take up too many bytes and are difficult to read. Ramda gives us `prop` and `path` to safely access objects. Let's see how they work:

```jsx
import { prop, path, pipe } from 'ramda'

const obj = { foo: 'bar', baz: { a: 1, b: 2 } }

const getFoo = prop('foo')
getFoo(obj) // => 'bar'

const getBazA = path(['baz', 'a'])
getBazA(obj) // => 1
```

Great! "But what about that is safe? All the properties you asked for are present!" Glad you asked:

```jsx
import { path, pipe } from 'ramda'

const obj = { foo: 'bar', baz: { a: 1, b: 2 } }

const getSomethingThatDoesNotExist = path([
  'foo',
  'bar',
  'baz',
  'theLastPropertyIPromise',
  'justKiddingOneMore'
])
getSomethingThatDoesNotExist(obj) // => undefined
```

Thanks Ramda! Hindquarters, yet again, saved. Note that `undefined`, a falsy value is returned. Very useful for presence checks! Let's apply our new learnings to our `<Entry />` component:

```jsx
  import { isNil, isEmpty, complement, prop } from 'ramda'

  const getUser = prop('user')
  const userIsPresent = pipe(
    getUser,
    complement(isNil)
  )

  const Entry = ({ client }) => (
    <Query query={currentUserQuery}>
      {({ loading, data }) => {
        if (isNil(loading) && isEmpty(data.user.posts))
          return <NoPosts />

        if (userIsPresent(data)) {
          const user = getUser(data)
          setErrorTrackingContext(user)
          getPostMetaData(user, client)
        }

        return (
          // code that renders things here
        )
      }}
    </Query>
  )
```

Looking better for sure. Further refactoring could be done in our second `if` condition. For fun, see if you can figure out how to use Ramda to bring that `if` into one function. Answer is at the end of this post!

## Prep Your Props With `evolve`

Transforming component props into something useful is common practice. Let's take a look at this example where we concat a first and last name as well as format a date:

```jsx
const NameAndDateDisplay = ({ date, firstName, lastName }) => (
  <>
    <div>
      Hello {firstName.toUpperCase()} {lastName.toUpperCase()}!
    </div>
    <div>It is {dayjs(date).format('M/D/YYYY dddd')}</div>
  </>
)
```

Straightforward, but there is something fishy about this code, though. Can you spot it? The problem is that it's a little _too_ straightforward. When working with real data, real API's, and real code that humans have written, things aren't always straightforward. Sometimes you are working on a project that consumes a third-party API and you don't have full control on what you get back from the server.

In these cases, we tend to throw all of our logic in our component bodies, like so:

```jsx
const NameAndDateDisplay = ({ date, firstName, lastName }) => {
  const formattedDate = formatDate(date)
  const formattedFirstName = formatFirstName(firstName)
  const formattedLastName = formatLastName(lastName)

  return (
    <>
      <div>
        Hello {firstName} {lastName}!
      </div>
      <div>It is {formattedDate}</div>
    </>
  )
}
```

This presents a few issues. Some very important logic is tied to the body of our component, making testing difficult. The only way to test those formatters is to render the component. Also, it's _really_ bloating the body of our component. In Rails you'll here "Fat models, skinny controllers"; an analogous term in React would be "Fat helpers, skinny component body".

Luckily, Ramda's `evolve` can really help us out. `evolve` takes two arguments; the first is an object whose values are functions, and the second argument is the object you want to operate on.

```jsx
import { evolve, toUpper } from 'ramda'

evolve({ foo: toUpper }, { foo: 'weeee' })
// => { foo: 'WEEEE' }
```

Pretty neat! Two important things to note about `evolve`: it's recursive and it doesn't operate on values you don't specify in the first argument.

```jsx
import { evolve, toUpper, add } from 'ramda'

const format = evolve({
  foo: toUpper,
  numbers: { a: add(2) },
  dontTouchMe: 'foobar'
})
format({ foo: 'weeee', numbers: { a: 3 } })
// => { foo: 'WEEEE', numbers: { a: 5 }, dontTouchMe: 'foobar' }
```

With this newfound knowledge, let's refactor our component:

```jsx
import { evolve, pipe } from 'ramda'

const prepProps = evolve({
  date: formatDate,
  firstName: formatFirstName,
  lastName: formatLastName
})

const NameAndDateDisplay = ({ date, firstName, lastName }) => (
  <>
    <div>
      Hello {firstName} {lastName}!
    </div>
    <div>It is {date}</div>
  </>
)

export default pipe(
  prepProps,
  NameAndDateDisplay
)
```

Sick! We have successfully split our formatting code away from our rendering code.

## Wrapping Up

React and Ramda are both incredibly powerful tools. Learning how they work and interact together can simplify and speed up development time.

Going forward, keep Ramda in mind when you find yourself copying & pasting helper libraries from one project to the next. Odds are, a Ramda function exists that can accomplish the same task, and more! There are many, many more Ramda functions not covered in this article. Look to [Ramda's documentation](https://ramdajs.com/docs/) to learn more.

## Refactoring Answer

Our second `if` condition, fully refactored:

```jsx
// setErrorTrackingContextAndGetPostMetaData.js
import { prop, pipe, complement, when, converge, curry, __ } from 'ramda'

const getUser = prop('user')
const userIsPresent = pipe(
  getUser,
  complement(isNil)
)
const curriedGetPostMetaData = curry(getPostMetaData)

const setErrorTrackingContextAndGetPostMetaData = client =>
  when(
    userIsPresent,
    converge(getUser, [
      setErrorTrackingContext,
      curriedGetPostMetaData(__, client)
    ])
  )

export default setErrorTrackingContextAndGetPostMetaData

// Entry.js
// in the body of <Entry />

// ...
setErrorTrackingContextAndGetPostMetaData(client)(data)
// ...
```