What follows is a piece of code that essentially toggles an array of filters (if the filter doesnt exist it adds it, if it does, it removes it).
What would you suggest is the best way to write the following imperative approach declaratively?
var selectedFilters = [ {name:"SomeName"} , ... ]
var inputFilter = {name:"OtherName"};
var indexFound = -1;
for (let i = 0; i < selectedFilters.length; i++) {
if (selectedFilters[i].name === inputFilter.name) {
indexFound = i;
}
}
if (indexFound != -1) {
selectedFilters.splice(indexFound, 1);
} else {
selectedFilters.push(inputFilter);
}
An idea would be to use filter first to weed out the item if it exists by name, then if the resulting array is equal to the original, push. But it still doesnt feel right.
3 Answers 3
Pure V State
There are two ways you can do this.
Pure
The first functional pure method first copies the array, then checks if the item to toggle exists then depending on that result adds or removes the item. Making sure that the added item is a copy, not a reference. It has no side effects but requires additional memory and CPU cycles.
const toggleItem = (itemDesc, items, prop = "name") => {
items = [...items];
const index = items.findIndex(item => itemDesc[prop] === item[prop]);
index > -1 ? items.splice(index, 1) : items.push({...itemDesc});
return items;
}
State
The second does not create a new array and keeps all references It is "functionally" impure and ensures that the changed state is available to all references to the original. It is considerably quicker and uses less memory.
const toggleItem = (itemDesc, items, prop = "name") => {
const index = items.findIndex(item => itemDesc[prop] === item[prop]);
index > -1 ? items.splice(index, 1) : items.push(itemDesc);
return items;
}
-
\$\begingroup\$ What's the point in
items = [...items]
? Making arrays generally immutable and each time producing a new sequence? Shouldn't it be implemented in a more structure-sharing manner? \$\endgroup\$bipll– bipll2018年03月27日 13:38:35 +00:00Commented Mar 27, 2018 at 13:38 -
\$\begingroup\$ @bipll The array needs to be copied to prevent side effects and keep the function pure. I do not code like that (its crazily inefficient and makes coding so much harder) but for many functional is the bee's knees and it is a requirement of the functional programming paradigm. \$\endgroup\$Blindman67– Blindman672018年03月27日 14:09:11 +00:00Commented Mar 27, 2018 at 14:09
Here is a functional version. Javascript has quite a lot functional type functions that can help you out here. It could probably made to be even nicer, but this was my first approach
function updateFilters(currentFilters, newFilter) {
const hasName = filter => filter.name === newFilter.name;
const foundIndex = currentFilters.findIndex(hasName);
return foundIndex === -1
? currentFilters.concat([newFilter])
: currentFilters.splice(foundIndex, 1);
}
Why not just filter with selectedFilters.filter(predName(inputFilter))
with a predicate predName = ({name: filterName}) => ({name}) => name === filterName
.