Cycle.js - How to get collection length in a collection item - javascript

I'm trying to add some behavior exclusively to the last item in a list in Cycle.js. I tried to use cycle-onionify to make a collection like so:
const List = makeCollection({
item: Child,
itemKey: (childState, index) => String(index),
itemScope: key => key,
collectSinks: instances => {
return {
onion: instances.pickMerge('onion'),
DOM: instances.pickCombine('DOM')
.map(itemVNodes => ul(itemVNodes))
}
}
});
I understand that lenses can be used to share state between components, but there doesn't seem to be a way to use lenses with a collection. I'm thinking I could pass the Collection length to the children so I could compare it with an id.
Is there something I am missing?

You can use lenses with makeCollection. Remember it returns a normal Cycle.js component that you can isolate. So if you want to add a boolean isLast you can do this like this:
function omit(obj, key) {
let tmp = { ...obj }; //Copy the object first
delete tmp[key];
return tmp;
}
const listLens = {
get: stateArray => stateArray.slice(0, -1).concat({
...stateArray[stateArray.length - 1],
isLast: true
}),
set: (stateArray, mappedArray) => mappedArray.slice(0, -1)
.concat(omit(mappedArray[mappedArray.length - 1], 'isLast'))
};
const List = isolate(
makeCollection({
item: Child,
itemKey: (childState, index) => String(index),
itemScope: key => key,
collectSinks: instances => ({
onion: instances.pickMerge('onion'),
DOM: instances.pickCombine('DOM')
.map(itemVNodes => ul(itemVNodes))
})
}),
{ onion: listLens, '*': null }
);
As a side note, if you want to apply a lens on each individual item, you can do so too, with the itemScope property. For example
itemScope: key => ({ onion: myLens, '*': key })

Related

How to add new properties into every object of an array

How can I add new 2 properties inside the array of objects? Those 2 properties should be added for every object inside the array. Here is the function:
selectTag(selectedProduct, selectedTag) {
this.selectedProducts.filter(item => {
item.id === selectedProduct.id
})
.map(item => {
item.tagId=selectedTag.id, item.tagTitle = selectedTag.title
})
},
dropdown
<b-dropdown aria-role="list">
<b-button
icon-right="caret-down"
class="ToolbarButton"
size="is-small"
>
<span> {{ selectedProduct.tagTitle }} </span>
</b-button>
<b-dropdownitem
v-for="selectedTag in selectedProduct.tags"
:key="selectedTag.id"
aria-role="listitem"
#click="selectTag(selectedProduct, selectedTag)"
>
{{ selectedTag.title }}
</b-dropdownItem>
I tried above function but it didn't work. map method should be fixed. I am trying to add tagId and tagTitle properties which will get value from drop down selection for every product row... How can be it fixed?
The map function indeed is wrong, you don't return anything, it should be like this:
.map(item => ({
...item,
tagId: selectedTag.id,
tagTitle: selectedTag.title
}))
or
.map(item => {
return {
...item,
tagId: selectedTag.id,
tagTitle: selectedTag.title
}
})
you can loop on the object and add your properties:
for(let obj of array) {
obj[key1] = value1;
obj[key2] = value2;
}
If this is in Vue.js, you cannot add properties to an object conventionally, as it will not be reactive. You need to use Vue.set:
selectTag: function(selectedProduct, selectedTag) {
for (var i = 0; i < this.selectedProducts.length; i++) {
if (this.selectedProducts[i].id === selectedProduct.id) {
this.$set(this.selectedProducts[i], "tagId", selectedTag.id);
this.$set(this.selectedProducts[i], "tagTitle", selectedTag.title);
}
}
}
Try this
var arr =[
{id: 1, turnName: "10_00_am"},
{id: 2, turnName: "10_00_am"},
{id: 3, turnName: "11_00_am"}
];
const addProps = (x) => {
x.prop1 = 'Alaska';
x.prop2 = 'Canada';
return x;
}
var newArr = arr.map(x => addProps(x));
console.log(newArr);
From the function you show, point out that the filter needs to store its result in some variable, since filter does not mutate the array in which it is executed. mapping doesn't work because you have to copy the object and then extend the new properties
If I understood your question correctly, please try the following example
const object = {
selectTag(selectedProduct, selectedTag) {
const data = this.selectedProducts.filter((item) => {
item.id === selectedProduct.id;
});
return data.map((entry) => ({
...entry,
tagId: selectedTag.id,
tagTitle: selectedTag.title,
}));
},
};
Use .map with object spread syntax.
.map(item => ({
...item,
item.tagId: selectedTag.id, item.tagTitle: selectedTag.title
}))
You can use ES6 object spread syntax to return a new updated item from map function.
Also, the object properties can be destructured in the function parameters itself to make it look concise and neat.
selectTag(({id}), ({id: tagId, title: tagTitle})) {
this.selectedProducts.filter(item => item.id === id)
.map(item => ({...item, tagId, tagTitle}))
}

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
}
};
}, {})

add value to specific object in array by index of array

How to add value to a specific object to the array by the index?
I wrote this, but of course, it creates a new object in the array, but I want to insert "errors" to an existing object with index (on screen it 0 index)
ipcRenderer.on('fileData', (event, data) => {
this.setState({jobs: [...this.state.jobs, {errors: data}]})
});
Then i wrote this:
ipcRenderer.on('fileData', (event, data) => {
this.state.jobs.forEach((item, index) => {
this.setState({jobs: [...this.state.jobs, {errors: item[index] = data}]
})
console.log(this.state)
})
});
It inserts a value into the object, but without a name and it still creates a new element in the array
I want the result to be like this:
jobs: [
0: {errors: 10, fileName:...}
]
If you know the index, you can just do
const jobs = this.state.jobs.slice(0);
jobs[index].errors = data;
this.setState({jobs});
Might have to do more than slice the array, might have to make a deep copy, but yeah, that should work.
Firstly you can make a copy of your array like
let jobsCopy = this.state.jobs
Then if you know the index you could just do like
jobsCopy[index].errors = 10
this.setState({
jobs: jobsCopy
})
You would need to know the index of the object you want to change. For example if you know it is the first item in the array you can do this:
const indexToChange = 0
this.setState(prevState => prevState.map((obj, i) => {
if(i === indexToChange) {
return {
...obj,
errors: data
}
} else {
return obj
}
}))

Create a conditional subset array with selective keys

I'm trying to create a conditional sub-set of an array.
I have an array allBooks which has properties such as type,author,id etc.
In a particular view I want to show only some of properties based on a condition.
For example; displaying summarized properties of all the books in stock.
Here is what I have tried:
let booksInStock: any[] = [];
this.allBooks.forEach(book => {
// Add only when book is in stock
if (book.isInStock) {
// Get only few keys from all the available keys
let temp: any = {
typeOfBook: book.targetType,
author: book.author,
bookId: book.id,
bookDisplayName: book.value,
bookName: book.bookName
};
// Add to the summarized or filtered list
booksInStock.push(temp);
}
});
Is there a more efficient way of doing it?
Using filter and map would be more semantic, like so:
let booksInStock = this.allBooks
.filter(book => book.isInStock)
.map(book => ({
typeOfBook: book.targetType,
author: book.author,
bookId: book.id,
bookDisplayName: book.value,
bookName: book.bookName
})
);
If efficiency is your priority however, a for loop is faster. See this link for an example: https://jsperf.com/map-vs-for-loop-performance/6
For example:
// This function is pretty generic, you can find one in e.g. underscore.js or Ramda:
const pluck = fields => item =>
Object
.keys(item)
.filter(key => fields.includes(key))
.reduce((result, key) => {
result[key] = item[key]
return result
}, {})
// Create filter+map+pluck -settings for different 'views':
const inStock = books =>
books.filter(b => b.isInStock)
.map(pluck(['targetType', 'author', 'id', 'value', 'name']))
// Invoke them:
const booksInStock = inStock([
{ isInStock: true, author:'harry', otherFIeld:'not-included' },
{ isInStock:false, author:'notharry'}
])

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