Mapping an object, converting into an array, then convert back to object - javascript

I have this code that i use in a redux reducer:
case 'REMOVE_FL_EVENT' :
return{
...state,
events: Object.keys(state.events).map(group => {
return state.events[group].filter(item => item.id !== action.id)
})
}
What happens here is that the state.events is an object, where every key is the name of the group of it's events, the value is an array with the events. What i want to do is when i convert the object into an array with map, if the filter happened convert back to it's original state, where state.events is not an array, but an object, with the original names of the keys.

There is no need to use map, you could use reduce only, something like:
{
...state,
events: Object.keys(state.events).reduce(
(obj, event) => ({
...obj,
[group]: state.events[group].filter(item => item.id !== action.id)
}),
{}
)
};
Update
The reduce has the following signature:
arr.reduce(callback[, initialValue])
So in our script, we are giving an empty object as the initial value for the accumulation.

You can use map/reduce for this purpose. First map it and then reduce it into an object.
case 'REMOVE_FL_EVENT' :
return{
...state,
events: Object.keys(state.events).map(group => {
return { [group]: state.events[group].filter(item => item.id !== action.id) }
}).reduce((obj, event) => Object.assign(obj, event), {})
}
The output will be an object with keys as groups. Let me know if it works.

With standard JS, you can use reduce to convert the array back to an obj:
arr.reduce((acc, o) => Object.assign(acc, o), {})
Using ramda.js you can filter Objects and their nested properties. https://ramdajs.com/docs/#filter

Related

Better way to turn object with named booleans into array with their uppercase keys?

I'm trying to convert the methods part of an Express Router into a one-dimensional array of uppercase string words, so this:
layer.route.methods: { get: true, post: true }
into:
methods: ["GET", "POST"]
This is what I came up with, but isn't there a more subtle way?
const methods = [];
!layer.route.methods.get || methods.push('GET');
!layer.route.methods.post || methods.push('POST');
This can be achieved with a combination of filter (to get only the methods with true) and map (to capitalize).
const methods = { get: true, post: true };
const result = Object.keys(methods).filter(method => methods[method]).map(method => method.toUpperCase());
console.log(result);
Turn down object to an array by using Object.keys(object) and then use loop either for or map to iterate and check if item exist in that converted array convert then to uppercase using item.toUpperCase()
One of the ways is to use reducer, to filter and modify values in one iteration.
const inputObject = { get: true, post: true };
const methods = Object.entries(inputObject).reduce(
(accumulator, [key, value]) => {
if (value) accumulator.push(key.toUpperCase());
return accumulator;
},
[]
);
console.log(methods);

filtering object and modify properties

I am having a newbie question and I have tried to read the manuals over and over and cannot figure it out.
so I have this code:
export function editSerier(data, products) {
return (dispatch) => {
const filteredProducts = Object.assign(
...Object.keys(products)
.filter(key => products[key].Artikelgrupp === data.Artikelgrupp)
.map(k => ({
[k]: products[k]:{
Beskrivning: data.Beskrivning,
kategori: data.kategori,
status: data.status,
synas: data.synas,
tillverkare: data.tillverkare,
titel: data.titel}
})
})
console.log(filteredProducts)
}
}
Where I want to filter the incoming object products by "Artikelgrupp" and then modify the existent properties of the remaining products with properties from "data".
However this code does not let me run it.
Does someone have any idea?
UPDATE:
just solved it by merging both objects
const filteredProducts = Object.assign(
...Object.keys(products)
.filter(key => products[key].Artikelgrupp === data.Artikelgrupp)
.map(k => ({
[k]: {...products[k], ...data}
}))
)
You have invalid JavaScript. If you want a nested object, you need { something: { } } and if you want to use a computed property name, you need to surround it with [].
So, this will work
export function editSerier(data, products) {
return dispatch => {
const filteredProducts = Object.assign(
...Object.keys(products)
.filter(key => products[key].Artikelgrupp === data.Artikelgrupp)
.map(k => ({
[k]: {
[products[k]]: {
Beskrivning: data.Beskrivning,
kategori: data.kategori,
status: data.status,
synas: data.synas,
tillverkare: data.tillverkare,
titel: data.titel
}
}
}))
);
console.log(filteredProducts);
};
}
If I understand correctly, you are wanting to obtain a single object which:
excludes all value objects of products where a Artikelgrupp field does not match data.Artikelgrupp and,
the specific fields Beskrivning, kategori, etc, from your data object are merged/copied into the product values of the resulting object
One solution to this would be as
/* Extract entry key/value pairs from products object */
Object.entries(products)
/* Reduce entry pairs to required object shape */
.reduce((result, [key, value]) => {
/* Substitute for prior filter step. Avoid overhead of array copy
between prior filter and this reduction. */
if(value.Artikelgrupp !== data.Artikelgrupp) {
return result;
}
/* Update result object, "upserting" fields of data object into
existing product value, for this reduce iteration */
return {
...result,
[ key ] : {
...value,
Beskrivning: data.Beskrivning,
kategori: data.kategori,
status: data.status,
synas: data.synas,
tillverkare: data.tillverkare,
titel: data.titel
}
};
}, {})

Filtering Arrays in a Reducer - Redux

Following a React tutorial, I see this code in a reducer to remove a message from an array using its ID:
Wouldn't this be better written as:
else if (action.type === 'DELETE_MESSAGE') {
return {
messages: [
...state.messages.filter(m => m.id === action.id)
],
};
};
I thought for a second that filter might modify state and return the same array but according to MDN it creates a new array.
Am I safe, and is my implementation correct?
Yes. It would actually be a very clean solution. The trick is that, in Array#filter, every element of an array is applied with a function that accepts more than one argument. Such a function, when returns a boolean value, is called predicate. In case of Array#filter (and in fact, some other methods of Array.prototype), the second argument is index of the element in source array.
So, given that you know the index, it's simply
(state, action) => ({
...state,
messages: state.messages.filter((item, index) => index !== action.index)
})
However, you don't know the index. Instead, you have a value of id property. In this case, you're right, you simply need to filter the source array against this id to only put elements that have value of id that is not equal to target id:
(state, action) => ({
...state,
messages: state.messages.filter(item => item.id !== action.id)
})
Take a note: no need to spread state.messages and put them back into a new array. Array#filter doesn't mutate the source array, which is nice.
So, it's !== instead of ===. You were close.

How to turn an observable of an array into an array of observables

Seems people only ever have trouble turning an array of observables into an observable of an array - not the other way round.
So, given an observable
Observable<Result[]>
how do I go from
Observable<Result[]> to Observable<Result>[] ?
The problem stems from the fact that I got an observable of items
items$: Observable<Item[]>
with
Item: {
value: number,
...
};
where value can take discrete values, say
values = [1, 2, 3]
I need to create an observable for each value, as each feeds a different component.
So I went ahead and did the following mapping
items$.map(items => values.map(value =>
({ value, items: items.filter(item => item.value === value) }))
which yields an
Observable<{ value: number, items: Item[] }[]>
However, in order to assign each value to its component I need
Observable<{ value: number, items: Item[] }>[]
How do I get there?
Just a small change in your code:
values.map(value =>
items$.map(items =>
({ value, items: items.filter(item => item.value === value) })
)
)
map operator actually creates a new observable

What is the shortest way to modify immutable objects using spread and destructuring operators

I'm looking for a pure function, to modify my immutable state object. The original state given as parameter must stay untouched. This is especially useful when working with frameworks like Redux and makes working with immutable object in javascript much easier. Especially since working with the object spread operator using Babel is already possible.
I did not found anything better than first copy the object, and than assign/delete the property I want like this:
function updateState(state, item) {
newState = {...state};
newState[item.id] = item;
return newState;
}
function deleteProperty(state, id) {
var newState = {...state};
delete newState[id];
return newState;
}
I feel like it could be shorter
Actions on state, where state is considered immutable.
Adding or Updating the value of a property:
// ES6:
function updateState(state, item) {
return Object.assign({}, state, {[item.id]: item});
}
// With Object Spread:
function updateState(state, item) {
return {
...state,
[item.id]: item
};
}
Deleting a property
// ES6:
function deleteProperty(state, id) {
var newState = Object.assign({}, state);
delete newState[id];
return newState;
}
// With Object Spread:
function deleteProperty(state, id) {
let {[id]: deleted, ...newState} = state;
return newState;
}
// Or even shorter as helper function:
function deleteProperty({[id]: deleted, ...newState}, id) {
return newState;
}
// Or inline:
function deleteProperty(state, id) {
return (({[id]: deleted, ...newState}) => newState)(state);
}
An ES6 solution, that has a bit more support is Object.assign:
const updateState = (state, item) => Object.assign({}, state, { [item.id]: item });
In a Map Function
To do this process within a map function (remove an attribute and add a new attribute on each object), given an array of objects -
const myArrayOfObjects = [
{id: 1, keyToDelete: 'nonsense'},
{id: 2, keyToDelete: 'rubbish'}
];
Delete the attribute keyToDelete, and add a new key newKey with the value "someVar".
myArrayOfObjects.map(({ keyToDelete, ...item}) => { ...item, newKey:'someVar'});
Updating the array to
[
{id: 1, newKey:'someVar'},
{id: 2, newKey:'someVar'}
]
See this great post for more information on the deletion method.
Instead of writing boilerplate code (as answered above: (({[id]: deleted, ...state}) => state)(state)) which is hard to read, you could use some library to do the same:
https://github.com/cah4a/immutable-modify
https://github.com/kolodny/immutability-helper
https://github.com/M6Web/immutable-set
https://github.com/bormind/immutable-setter
For example:
import {remove} from 'immutable-modify'
function updateState(state, item) {
return remove(state, item.id)
}
It's also supports any nested updates:
import {set} from 'immutable-modify'
function updateState(state, item) {
return set(state, 'user.products', (products) => ({
...products,
items: products.items.concat(item),
lastUpdate: Date.now()
}))
}
Try:
const { id, ...noId } = state;
And test:
console.log(noId);
Removing item from an array, just use filter ;)
CASE 'REMOVE_ITEM_SUCCESS':
let items = state.items.filter(element => element._id !== action.id);
return {
...state,
items
}

Categories