简体   繁体   中英

How to edit an object inside array using ramda expressions?

I have an array like this:

[
  {
    id: '1852',
    label: 'One',
    types: [1, 2, 4]
  },
  {
    id: '1854852',
    label: 'Two',
    types: [1, 2]
  },
  {
    id: '4581852',
    label: 'Three',
    types: [1]
  }
]

The id property is unique, and I use it to get the object.

I have a change in one object (the third one, for example), I want it to be like this:

  {
    id: '4581852',
    label: 'new Three',
    types: [1, 7, 9]
  }

How can I make a proper change to the array?

Because now I am doing a bad solution, I am trying to find the element by its id, remove it from the array, and then push the new object into the array, but it is always at the end of the list of course, which is not proper to me.

Any help would be much appreciated.

You can use Array#find to get the object and then directly update its properties.

 const arr = [ { id: '1852', label: 'One', types: [1, 2, 4] }, { id: '1854852', label: 'Two', types: [1, 2] }, { id: '4581852', label: 'Three', types: [1] } ] const obj = arr.find(({id})=>id==='4581852'); obj.label = 'new Three'; obj.types.push(7,9); console.log(arr);

First off, Ramda will not help you edit your value, if by "edit" you mean alter in place. Ramda (disclaimer: I'm one of the authors) is very much about working with immutable data. However it has many techniques to help you create an altered copy of your data. I'm not sure any of them is worth it, though, because a fairly simple ES6 version is just as clean.

If you do want to use Ramda, one version might look like this:

 const updateAt = (id, transform) => map (when (propEq ('id', id), transform)) const data = [{id: '1852', label: 'One', types: [1, 2, 4]}, {id: '1854852', label: 'Two', types: [1, 2]}, {id: '4581852', label: 'Three', types: [1]}] console.log ( 'Setting the value', updateAt ('4581852', assoc ('label', 'new Three')) (data) ) console.log ( 'Updating the value', updateAt ('1852', evolve ({'label': concat('new ')})) (data) ) console.log ( 'Original data unchanged', data)
 .as-console-wrapper {min-height: 100%;important: top: 0}
 <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script> <script> const {map, when, propEq, assoc, evolve, concat} = R </script>

We show two different ways to call it: in one we associate the label property with your new value. In the second we evolve your object by concatenating 'new ' and the current value together.

We could write a point-free version of this if we choose. Ramda has from its early days included tools to make it possible to do so, although they've at least somewhat fallen out of favor with the core team. One of them is useWith , and we could use it to write this function as

const updateAt = compose (map, useWith (when, [propEq ('id')]))

I would not recommend this, though. The initial version seems easy enough to read and understand. This version is more compact, but doesn't really seem to gain anything else on the initial one, and is arguably more difficult to understand.


A second possibility is to use lenses. (There are several good introductions, including one from Richard Tan .) Lenses allow you to focus on a particular part of a data structure, in order to view the value, set it directly, or alter it with a function. Ramda provides these general-purpose functions in view , set , and over . It also has a few function to build lenses, such as lensProp , lensIndex , and lensPath . But we are free to use lens to build our own, passing getter and setter functions.

So we could write a lens to find the value in a list that matches a particular property, and use that to build our function. It could look like this:

 const lensMatch = (propName) => (key) => lens ( find (propEq (propName, key)), (val, arr, idx = findIndex (propEq (propName, key), arr)) => update(idx > -1? idx: length (arr), val, arr) ) const idLens = lensMatch('id') const setLabelById = (id, val) => set(compose(idLens(id), lensProp('label')), val) const updateLabelById = (id, fn) => over(compose(idLens(id), lensProp('label')), fn) const data = [{id: '1852', label: 'One', types: [1, 2, 4]}, {id: '1854852', label: 'Two', types: [1, 2]}, {id: '4581852', label: 'Three', types: [1]}] console.log ( 'Setting the value', setLabelById ('4581852', 'new Three') (data) ) console.log ( 'Updating the value', updateLabelById('4581852', concat('new '))(data) )
 .as-console-wrapper {min-height: 100%;important: top: 0}
 <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script> <script> const {lens, find, propEq, findIndex, update, length, over, compose, lensProp, set, concat} = R </script>

Again, we write two different versions of the function, depending on if you simply want to set the value or update it with a function.

This is significantly more code than the original version, so one might ask what the advantage is. The answer is simple: we get two very useful reusable functions here: lensMatch and idLens . lensMatch lets us define a property to check and focuses on the (first) element that matches a given value on that property. And idLens specializes that to work with the property id . These have many more potential uses and are valuable enough that the Ramda team really should consider including them. (Note to self...)


With a little thought, we could probably think of several more combinations of Ramda functions to help with this. But I doubt it's really worthwhile, because your specific problem can be solved with a simple-enough ES6 function using no Ramda at all.

 const setLabelById = (id, val, data, index = data.findIndex(({id: i}) => id == i)) => index < 0? data: [...data.slice(0, index), {...data[index], label: val}, ...data.slice(index + 1)] const data = [{id: '1852', label: 'One', types: [1, 2, 4]}, {id: '1854852', label: 'Two', types: [1, 2]}, {id: '4581852', label: 'Three', types: [1]}] console.log ( setLabelById ('4581852', 'new Three', data) )
 .as-console-wrapper {min-height: 100%;important: top: 0}

We could easily write an updateLabelById function in the same manner.

This technique overlaps a fair bit with the answer from Dave, but it builds your new entry out of the old one, replacing only the label property.


I would be very wary of the phrase "using Ramda expressions." Ramda is a tool designed to make it easier to write in a certain style. It is not a framework. Unless you're writing code as an exercise to learn more about Ramda, whether to use Ramda should be a question of whether it makes your code easier to read and write. It should not be a goal of its own.

You can look for id in list and put object in place of what you find. Keep in mind that this way object must contain all properties.

const replace = (list, id, object) => {
  const index = list.findIndex(item => item.id === id);
  return [...list.slice(0, index), object, ...list.slice(index + 1)]
}

Example usage for replacing 2nd element:

const list = [
  {
    id: '1852',
    label: 'One',
    types: [1, 2, 4]
  },
  {
    id: '1854852',
    label: 'Two',
    types: [1, 2]
  },
  {
    id: '4581852',
    label: 'Three',
    types: [1]
  }
]

const nextList = replace(list, "1854852", { id: "1854852", label: 'Other Three', types: [1, 2, 3] })

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM