Ramda Chops: Safely Accessing Properties
Thanks to @joshsloat and @zerkms for their review of this post.
One of the most prevalent causes of bugs I’ve seen in latter-day JavaScript
revolves around expectations with regard to data modeling. With the rise of
react, redux, et al, many of us
store our application state in an object whose keys and hierarchy can easily
change, leaving us sometimes with or without values that were in fact expected:
for example, undefined is not a function
or trying to call .map(...)
on a
non-mappable data type (such as null
or undefined
). While there are any
number of solutions for this issue that might even include diving into algebraic
data types, the ramda library gives us a few
helper methods that we can use right away to dig into our data structures and
extract values:
Other ramda posts:
prop
& propOr
What happens normally if you expect an array, try to access the third item
(index position 2), but are actually provided undefined
instead of an array?
const arr = undefined
2] // TypeError is thrown arr[
What happens if you try to access the length
property on what you think should
be an array but ends up being null
or undefined
?
const arr = null
.length // TypeError is thrown arr
One solution is to do the “value or default” approach to keep the errors at bay:
const arr = undefined
const xs = arr || []
2] // undefined
xs[.length // 0 xs
An approach we could take to avoid the errors being thrown would be to use
ramda’s prop
helper:
import prop from 'ramda/src/prop'
const arr = undefined
prop(2, arr) // undefined
prop('length', arr) // undefined
Ramda’s length
function would accomplish
a similar goal for prop('length')
.
But if we want a default to be returned in lieu of our data not being present,
we can turn to propOr
:
import propOr from 'ramda/src/propOr'
const arr = undefined
propOr({}, 2, arr) // {}
propOr(0, 'length', arr) // 0
If you need to select multiple properties without fear, then the
props
or
pick
functions may be for you.
path
& pathOr
What if we are working in a deeply nested data structure where multiple keys in
our hierarchy may or may not exist? Enter path
and pathOr
. These work similarly to prop
and propOr
except that they use an array syntax to dive into data structures
and ultimately check for a value, whereas the prop
family checks for a
property’s presence.
import path from 'ramda/src/path'
const data = {
courses: {
abc123: {
title: 'How To Build a Tiny House',
dueAt: '2018-01-30'
}
}
}
// getCourseTitle :: String -> String | undefined
const getCourseTitle = courseId =>
path(['courses', courseId, 'title'])
getCourseTitle('abc123')(data) // "How To Build a Tiny House"
getCourseTitle('def456')(data) // undefined
Try this code in the ramda REPL
Or if we’d always like to default to a value, we can use pathOr
:
import pathOr from 'ramda/src/path'
const data = {
courses: {
abc123: {
title: 'How To Build a Tiny House',
dueAt: '2018-01-30'
}
}
}
// getCourseTitle :: String -> String
const getCourseTitle = courseId =>
pathOr('My Course', ['courses', courseId, 'title'])
getCourseTitle('abc123')(data) // "How To Build a Tiny House"
getCourseTitle('def456')(data) // "My Course"
Try this code in the ramda REPL
As I said before, there are many different ways to solve this problem, but
I’ve found the propOr
and pathOr
family of ramda functions to be a great
starting point.
Until next time,
Robert