We can certainly write a fairly readable version in Ramda:
const updateItem = (id, payload) => map (
when (propEq ('id', id), mergeLeft (payload))
)
const items = [{foo: 'a', id: 1}, {foo: 'b', id: 2}, {foo: 'c', id: 1}, {foo: 'd', id: 4}]
console .log (updateItem (1, {bar: 'x'}) (items)) // updates all with id of 1
console .log (updateItem (2, {bar: 'x'}) (items))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
<script> const {map, when, propEq, mergeLeft} = R </script>
We can read this as map
over our input a function that when
the id
property equals (propEq
) the id
supplied, perform a mergeLeft
on the payload
and the current value. when
has an implicit understanding that if the condition doesn't match, it returns the value intact.
If you want an entirely point-free version of the initial approach, we could use the somewhat obscure useWith
, like this:
const updateItems = compose (map, useWith (when, [propEq ('id'), mergeLeft]))
I would probably prefer to write that like this, though:
const updateItem = useWith (when, [propEq ('id'), mergeLeft])
const updateItems = compose (map, updateItem)
If you want to supply all arguments together, we could do this:
const updateItem = (id, payload, xs) =>
map (when (propEq ('id', id), mergeLeft (payload)), xs)
// ...
updateItem (1, {bar: 'x'}, items)
OOPS!: The following is completely wrong; see the comment on another answer
All of these make the assumption that we are testing every element. It does not assume that the id
is unique. If you want to do that, the answer from Chad S. would work, but I would prefer to do it slightly differently:
// ***********************************************
// * NOTE: This is entirely broken. Do not use. *
// ***********************************************
const updateItems = (id, payload, xs) =>
adjust (findIndex (propEq ('id', id), xs), mergeLeft (payload), xs)
const items = [{foo: 'a', id: 1}, {foo: 'b', id: 2}, {foo: 'c', id: 1}, {foo: 'd', id: 4}]
console .log (updateItems (1, {bar: 'x'}, items)) // only the first one.
console .log (updateItems (2, {bar: 'x'}, items))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
<script> const {adjust, findIndex, propEq, mergeLeft} = R </script>
We could also move toward point-free on this version, but I think it would end up pretty ugly. We could make xs
implicit trivially, but anything else would make it less readable to my mind.
Update
After that poor answer, I thought I owed an attempt at creating an only-the-first-match solution that actually works. I personally wouldn't try to do this nearly so close to point-free, but here's one appoach:
const updateItems = (id, payload) => chain (
ifElse (equals (-1), nthArg (1), flip (adjust) (mergeLeft (payload))),
findIndex (propEq ('id', id))
)
const items = [{foo: 'a', id: 1}, {foo: 'b', id: 2}, {foo: 'c', id: 1}, {foo: 'd', id: 4}]
console .log (updateItems (1, {bar: 'x'}) (items)) // only the first of multiple matches.
console .log (updateItems (2, {bar: 'x'}) (items)) // one match
console .log (updateItems (7, {bar: 'x'}) (items)) // no matches
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
<script> const {chain, ifElse, equals, nthArg, flip, adjust, mergeLeft, findIndex, propEq} = R </script>
I would still choose the version at the top unless I found actual performance problems with it.