Arrays: Conditionally Displayed Elements

I work with Redux, and I often have to write reducers with expressions like this:

return users.map(user => user.id !== selectedUserID ? user : { ... user, removed: false } ); 

The goal should be clear enough: only change the element in the array that has the given identifier when copying the rest, since they are . Also note that ordering is important, otherwise I could just use the filter() function.

This snippet causes a no-confusing-arrow error in eslint, which makes sense to me. This can also be easily solved by adding a parenthesis around the body of the arrow function, so there is nothing to do here:

 return users.map(user => ( user.id !== selectedUserID ? user : { ... user, removed: false } )); 

I also want to parse this code through prettier , which automatically removes the parentheses around the arrow function body, returning to version 1 of the snippet.

The obvious solution here is to write the arrow function in a verbal way:

 return users.map(user => { if(user.id !== selectedUserID) { return user; // unmodified user } return { ... user, removed: false }; }); 

but I honestly think this is too awkward.

Besides the specific context and tools used (eslint and prettier, which can be configured differently / disabled / independently), is there a better way to write this?

In my wildest dreams, there is a function with a signature similar to:

 Array.mapIf(callback, condition) 

which loops through all the elements in the array and calls the callback function only for those that satisfy the specified condition , returning other unmodified elements.

I could write the same function, but maybe there is something already existing in other functional languages ​​that might be worth a look at the general culture / inspiration.

+5
source share
4 answers

There is no such native function, because you can easily implement it yourself:

 const mapWhen = (p, f) => xs => xs.map(x => p(x) ? f(x) : x); const users = [ {id: 1, removed: true}, {id: 2, removed: true}, {id: 3, removed: true} ]; console.log( mapWhen( ({id}) => id === 2, user => Object.assign({}, user, {removed: false}) ) (users) ); 

I chose mapWhen as the name instead of mapIf , because the latter would mean that there is an else branch.

With a mature functional language, you are likely to solve this problem with a functional lens . I think, however, mapWhen enough for your case and more idiomatic.

+3
source

In ruby ​​you will return something like

 users.dup.select {|u| u.id == selected_user_id }.each {|u| u.removed = false } 

dup important in this case because in Redux you want to return a new array and not modify the original, so you need to create a copy of the original first. See fooobar.com/questions/297216 / ... (note that the discussion is very similar to this)

In your case, I would use:

 const users = [ {id: 1, removed: true}, {id: 2, removed: true}, {id: 3, removed: true} ]; const selectedUserId = 2; console.log( users.map( (user) => ({ ...user, removed: (user.id !== selectedUserId ? false : user.removed) }) ) ); 
+2
source

You can use Object.assign , which will add a property if necessary.

 return users.map(user => Object.assign({}, user, user.id === selectedUserID && { removed: false }) ); 
0
source

I will present this as a complement to the @ftor practical answer - this snippet shows how common functions can be used to achieve a specific result

 const identity = x => x const comp = ( f, g ) => x => f ( g ( x ) ) const compose = ( ...fs ) => fs.reduce ( comp, identity ) const append = ( xs, x ) => xs.concat ( [ x ] ) const transduce = ( ...ts ) => xs => xs.reduce ( compose ( ...ts ) ( append ), [] ) const when = f => k => ( acc, x ) => f ( x ) ? k ( acc, x ) : append ( acc, x ) const mapper = f => k => ( acc, x ) => k ( acc, f ( x ) ) const data0 = [ { id: 1, remove: true } , { id: 2, remove: true } , { id: 3, remove: true } ] const data1 = transduce ( when ( x => x.id === 2) , mapper ( x => ( { ...x, remove: false } ) ) ) (data0) console.log (data1) // [ { id: 1, remove: true } // , { id: 2, remove: false } // , { id: 3, remove: true } // ] 

However, I think that we could improve when with a more general behavior, all we need is a little help from some of the invented types of Left and Right - to expand the full code fragment after this passage

 const Left = value => ({ fold: ( f, _ ) => f ( value ) }) const Right = value => ({ fold: ( _, f ) => f ( value ) }) // a more generic when const when = f => k => ( acc, x ) => f ( x ) ? k ( acc, Right ( x ) ) : k ( acc, Left ( x ) ) // mapping over Left/Right mapper ( m => m.fold ( identity // Left branch , x => ( { ...x, remove: false } ) // Right branch ) ) 

 const Left = value => ({ fold: ( f, _ ) => f ( value ) }) const Right = value => ({ fold: ( _, f ) => f ( value ) }) const identity = x => x const comp = ( f, g ) => x => f ( g ( x ) ) const compose = ( ...fs ) => fs.reduce ( comp, identity ) const append = ( xs, x ) => xs.concat ( [ x ] ) const transduce = ( ...ts ) => xs => xs.reduce ( compose ( ...ts ) ( append ), [] ) const when = f => k => ( acc, x ) => f ( x ) ? k ( acc, Right ( x ) ) : k ( acc, Left ( x ) ) const mapper = f => k => ( acc, x ) => k ( acc, f ( x ) ) const data0 = [ { id: 1, remove: true } , { id: 2, remove: true } , { id: 3, remove: true } ] const data1 = transduce ( when ( x => x.id === 2 ) , mapper ( m => m.fold ( identity , x => ( { ...x, remove: false } ) ) ) ) (data0) console.log (data1) // [ { id: 1, remove: true } // , { id: 2, remove: false } // , { id: 3, remove: true } // ] 

So your final function will probably look something like this.

 const updateWhen = ( f, records, xs ) => transduce ( when ( f ) , mapper ( m => m.fold ( identity , x => ( { ...x, ...records } ) ) ) ) ( xs ) 

You will call it in your gearbox as follows

 const BakeryReducer = ( donuts = initState , action ) => { switch ( action.type ) { case ApplyGlaze: return updateWhen ( donut => donut.id === action.donutId ) , { glaze: action.glaze } , donuts ) // ... default: return donuts } } 
0
source

Source: https://habr.com/ru/post/1273236/


All Articles