Extracting specific values from an array within an object - javascript

I'm setting up a test to ensure that a faceted Solr query 'contents' are correctly displayed within a page element, using javascript.
The Solr query result, which I've named "ryanlinkstransmissionpage", is;
{ Transmission: [ 'Manual', 12104, 'Automatic', 9858 ] }
What I would like to do is extract the 'Manual' and 'Automatic' only, so I can then test that these values are displayed on a page.
However, it is more the functionality involved in this that I cannot get my head around, as I will be using this method on other Solr query results.
To possibly complicate things, this Solr query result "ryanlinkstransmissionpage" is from a dynamic 'live' Solr, so the values may change each time it's run (so there may be more or less values within this array when it's tested on the following day for example).
I've tried a few javascript commands, but to no avail.
JSON.parse(ryanlinkstransmissionpage)
JSON.stringify(ryanlinkstransmissionpage)
Object.values(ryanlinkstransmissionpage)
Any help would be greatly appreciated. Thanks.

If possible, i highyl recommend changing the transmission field to be an object, rather than an array. That will give you far greater ability to read the data within.
Ignoring that, are you looking to extract the string values and the number values that follow them? ie. "Manual" and "12104"? Or are you simply trying to assert that the string values are present on the page?
Either way, here are two possible approaches.
const ryanlinkstransmissionpage = { Transmission: [ 'Manual', 12104, 'Automatic', 9858 ] };
// Pull out the string values
const strngVals = ryanlinkstransmissionpage.Transmission.filter(val => typeof val === 'string');
// Pull out the string values and the numbers that follow
const strngNumVals = ryanlinkstransmissionpage.Transmission.reduce((keyVals, val, idx, srcArr) => {
if (typeof val === 'string') keyVals[val] = srcArr[idx + 1];
return keyVals;
}, {});
The reduce approach is not stable or robust to changes in data provided from this Solr query result you refer to, nor is it tested. #shrug

Javascript has a built in method called Array.prototype.find(() =>). If you just want to check if this value exists to ensure its on the page, you can simply do:
const ryanlinkstransmissionpage = { Transmission: [ 'Manual', 12104, 'Automatic', 9858 ] };
const manual = ryanlinkstransmissionpage.Transmission.find((ele) => ele === 'Manual'); // returns 'Manual'
const automatic = ryanlinkstransmissionpage.Transmission.find((ele) => ele === 'Automatic'); // returns 'Automatic'
console.log(automatic);
console.log(manual);
// or
const findInArray = (arr, toFind) => {
const result = arr.find((ele) => ele === toFind);
return !!result;
}
console.log(findInArray(ryanlinkstransmissionpage.Transmission, 'Automatic')); // true
console.log(findInArray(ryanlinkstransmissionpage.Transmission, 'HelloWorld')); // false
console.log(findInArray(ryanlinkstransmissionpage.Transmission, 'Manual')); // true

Related

Js/React : Find ONLY the elements whitin an array that matches X condition to get X results

Im getting different elements from an array that comes from the backend like so :
data = [{id: '1', collection: 32, isNew: true},{id: '5', collection: 22, isNew: false}, .... ]
The user is allowed to select in a massive way differents Ids, and edit them, and I need to show a different modal for each different cases :
The conditions to show the differents modals are based on : If I select only the ones that has isNew = true, if I select only the ones with isNew= false, or if I select both Id cases isNew=false and isNew=true.
So far Ive tried this :
const getNewInfo = data.some(item => item.isNew)
if(getNewInfo) {
return this.explainNewInfo(true)
} else if(!getNewInfo) {
return this.explainNewInfo(false)
} else {
return this.explainNewInfo()
}
I also tried with the filter method and push to a new array, but it relies on the same logic at the end. For the first both cases, it works fine, but in the 3rd case, I cant get in, since whenever matches that isNew is true, it gets in there, and discard all the rest of posibilites.
Each function that is being called on the if else, is a function that recives or not a parameter indicating if is necessary to show a certain modal for the 3 differentes cases.
You can use every function on an array to check for all objects. Read about every.
const isAllNewInfo = data.every(item => item.isNew)
const isAllOldInfo = data.every(item => !item.isNew)
if (isAllNewInfo) {
return this.explainNewInfo(true)
} else if(isAllOldInfo) {
return this.explainNewInfo(false)
} else {
return this.explainNewInfo()
}

Is there any option to find whether a text is present in either one of the field in firestore [duplicate]

From the docs:
You can also chain multiple where() methods to create more specific queries (logical AND).
How can I perform an OR query?
Example:
Give me all documents where the field status is open OR upcoming
Give me all documents where the field status == open OR createdAt <= <somedatetime>
OR isn't supported as it's hard for the server to scale it (requires keeping state to dedup). The work around is to issue 2 queries, one for each condition, and dedup on the client.
Edit (Nov 2019):
Cloud Firestore now supports IN queries which are a limited type of OR query.
For the example above you could do:
// Get all documents in 'foo' where status is open or upcmoming
db.collection('foo').where('status','in',['open','upcoming']).get()
However it's still not possible to do a general OR condition involving multiple fields.
With the recent addition of IN queries, Firestore supports "up to 10 equality clauses on the same field with a logical OR"
A possible solution to (1) would be:
documents.where('status', 'in', ['open', 'upcoming']);
See Firebase Guides: Query Operators | in and array-contains-any
suggest to give value for status as well.
ex.
{ name: "a", statusValue = 10, status = 'open' }
{ name: "b", statusValue = 20, status = 'upcoming'}
{ name: "c", statusValue = 30, status = 'close'}
you can query by ref.where('statusValue', '<=', 20) then both 'a' and 'b' will found.
this can save your query cost and performance.
btw, it is not fix all case.
I would have no "status" field, but status related fields, updating them to true or false based on request, like
{ name: "a", status_open: true, status_upcoming: false, status_closed: false}
However, check Firebase Cloud Functions. You could have a function listening status changes, updating status related properties like
{ name: "a", status: "open", status_open: true, status_upcoming: false, status_closed: false}
one or the other, your query could be just
...where('status_open','==',true)...
Hope it helps.
This doesn't solve all cases, but for "enum" fields, you can emulate an "OR" query by making a separate boolean field for each enum-value, then adding a where("enum_<value>", "==", false) for every value that isn't part of the "OR" clause you want.
For example, consider your first desired query:
Give me all documents where the field status is open OR upcoming
You can accomplish this by splitting the status: string field into multiple boolean fields, one for each enum-value:
status_open: bool
status_upcoming: bool
status_suspended: bool
status_closed: bool
To perform your "where status is open or upcoming" query, you then do this:
where("status_suspended", "==", false).where("status_closed", "==", false)
How does this work? Well, because it's an enum, you know one of the values must have true assigned. So if you can determine that all of the other values don't match for a given entry, then by deduction it must match one of the values you originally were looking for.
See also
in/not-in/array-contains-in: https://firebase.google.com/docs/firestore/query-data/queries#in_and_array-contains-any
!=: https://firebase.googleblog.com/2020/09/cloud-firestore-not-equal-queries.html
I don't like everyone saying it's not possible.
it is if you create another "hacky" field in the model to build a composite...
for instance, create an array for each document that has all logical or elements
then query for .where("field", arrayContains: [...]
you can bind two Observables using the rxjs merge operator.
Here you have an example.
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/merge';
...
getCombinatedStatus(): Observable<any> {
return Observable.merge(this.db.collection('foo', ref => ref.where('status','==','open')).valueChanges(),
this.db.collection('foo', ref => ref.where('status','==','upcoming')).valueChanges());
}
Then you can subscribe to the new Observable updates using the above method:
getCombinatedStatus.subscribe(results => console.log(results);
I hope this can help you, greetings from Chile!!
We have the same problem just now, luckily the only possible values for ours are A,B,C,D (4) so we have to query for things like A||B, A||C, A||B||C, D, etc
As of like a few months ago firebase supports a new query array-contains so what we do is make an array and we pre-process the OR values to the array
if (a) {
array addObject:#"a"
}
if (b) {
array addObject:#"b"
}
if (a||b) {
array addObject:#"a||b"
}
etc
And we do this for all 4! values or however many combos there are.
THEN we can simply check the query [document arrayContains:#"a||c"] or whatever type of condition we need.
So if something only qualified for conditional A of our 4 conditionals (A,B,C,D) then its array would contain the following literal strings: #["A", "A||B", "A||C", "A||D", "A||B||C", "A||B||D", "A||C||D", "A||B||C||D"]
Then for any of those OR combinations we can just search array-contains on whatever we may want (e.g. "A||C")
Note: This is only a reasonable approach if you have a few number of possible values to compare OR with.
More info on Array-contains here, since it's newish to firebase docs
If you have a limited number of fields, definitely create new fields with true and false like in the example above. However, if you don't know what the fields are until runtime, you have to just combine queries.
Here is a tags OR example...
// the ids of students in class
const students = [studentID1, studentID2,...];
// get all docs where student.studentID1 = true
const results = this.afs.collection('classes',
ref => ref.where(`students.${students[0]}`, '==', true)
).valueChanges({ idField: 'id' }).pipe(
switchMap((r: any) => {
// get all docs where student.studentID2...studentIDX = true
const docs = students.slice(1).map(
(student: any) => this.afs.collection('classes',
ref => ref.where(`students.${student}`, '==', true)
).valueChanges({ idField: 'id' })
);
return combineLatest(docs).pipe(
// combine results by reducing array
map((a: any[]) => {
const g: [] = a.reduce(
(acc: any[], cur: any) => acc.concat(cur)
).concat(r);
// filter out duplicates by 'id' field
return g.filter(
(b: any, n: number, a: any[]) => a.findIndex(
(v: any) => v.id === b.id) === n
);
}),
);
})
);
Unfortunately there is no other way to combine more than 10 items (use array-contains-any if < 10 items).
There is also no other way to avoid duplicate reads, as you don't know the ID fields that will be matched by the search. Luckily, Firebase has good caching.
For those of you that like promises...
const p = await results.pipe(take(1)).toPromise();
For more info on this, see this article I wrote.
J
OR isn't supported
But if you need that you can do It in your code
Ex : if i want query products where (Size Equal Xl OR XXL : AND Gender is Male)
productsCollectionRef
//1* first get query where can firestore handle it
.whereEqualTo("gender", "Male")
.addSnapshotListener((queryDocumentSnapshots, e) -> {
if (queryDocumentSnapshots == null)
return;
List<Product> productList = new ArrayList<>();
for (DocumentSnapshot snapshot : queryDocumentSnapshots.getDocuments()) {
Product product = snapshot.toObject(Product.class);
//2* then check your query OR Condition because firestore just support AND Condition
if (product.getSize().equals("XL") || product.getSize().equals("XXL"))
productList.add(product);
}
liveData.setValue(productList);
});
For Flutter dart language use this:
db.collection("projects").where("status", whereIn: ["public", "unlisted", "secret"]);
actually I found #Dan McGrath answer working here is a rewriting of his answer:
private void query() {
FirebaseFirestore db = FirebaseFirestore.getInstance();
db.collection("STATUS")
.whereIn("status", Arrays.asList("open", "upcoming")) // you can add up to 10 different values like : Arrays.asList("open", "upcoming", "Pending", "In Progress", ...)
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots, #Nullable FirebaseFirestoreException e) {
for (DocumentSnapshot documentSnapshot : queryDocumentSnapshots) {
// I assume you have a model class called MyStatus
MyStatus status= documentSnapshot.toObject(MyStatus.class);
if (status!= null) {
//do somthing...!
}
}
}
});
}

what is the equivalent of a reduce in javascript

I'm a backend dev moved recently onto js side. I was going through a tutorial and came across the below piece of code.
clickCreate: function(component, event, helper) {
var validExpense = component.find('expenseform').reduce(function (validSoFar, inputCmp) {
// Displays error messages for invalid fields
inputCmp.showHelpMessageIfInvalid();
return validSoFar && inputCmp.get('v.validity').valid;
}, true);
// If we pass error checking, do some real work
if(validExpense){
// Create the new expense
var newExpense = component.get("v.newExpense");
console.log("Create expense: " + JSON.stringify(newExpense));
helper.createExpense(component, newExpense);
}
}
Here I tried to understand a lot on what's happening, there is something called reduce and another thing named validSoFar. I'm unable to understand what's happening under the hood. :-(
I do get the regular loops stuff as done in Java.
Can someone please shower some light on what's happening here. I should be using this a lot in my regular work.
Thanks
The reduce function here is iterating through each input component of the expense form and incrementally mapping to a boolean. If you have say three inputs each with a true validity, the reduce function would return:
true && true where the first true is the initial value passed into reduce.
true && true and where the first true here is the result of the previous result.
true && true
At the end of the reduction, you're left with a single boolean representing the validity of the entire, where by that if just a single input component's validity is false, the entire reduction will amount to false. This is because validSoFar keeps track of the overall validity and is mutated by returning the compound of the whether the form is valid so far and the validity of the current input in iteration.
This is a reasonable equivalent:
var validExpense = true;
var inputCmps = component.find('expenseform')
for (var i = 0; i < inputCmps.length; i++) {
// Displays error messages for invalid fields
inputCmp.showHelpMessageIfInvalid();
if (!inputCmp.get('v.validity').valid) {
validExpense = false;
}
}
// Now we can use validExpense
This is a somewhat strange use of reduce, to be honest, because it does more than simply reducing a list to a single value. It also produces side effects (presumably) in the call to showHelpMessageIfInvalid().
The idea of reduce is simple. Given a list of values that you want to fold down one at a time into a single value (of the same or any other type), you supply a function that takes the current folded value and the next list value and returns a new folded value, and you supply an initial folded value, and reduce combines them by calling the function with each successive list value and the current folded value.
So, for instance,
var items = [
{name: 'foo', price: 7, quantity: 3},
{name: 'bar', price: 5, quantity: 5},
{name: 'baz', price: 19, quantity: 1}
]
const totalPrice = items.reduce(
(total, item) => total + item.price * item.quantity, // folding function
0 // initial value
); //=> 65
It does not make sense to use reduce there and have side effects in the reduce. Better use Array.prototype.filter to get all invalid expense items.
Then use Array.prototype.forEach to produce side effect(s) for each invalid item. You can then check the length of invalid expense items array to see it your input was valid:
function(component, event, helper) {
var invalidExpenses = component.find('expenseform').filter(
function(ex){
//return not valid (!valid)
return !ex.get('v.validity').valid
}
);
invalidExpenses.forEach(
//use forEach if you need a side effect for each thing
function(ex){
ex.showHelpMessageIfInvalid();
}
);
// If we pass error checking, do some real work
if(invalidExpenses.length===0){//no invalid expense items
// Create the new expense
var newExpense = component.get("v.newExpense");
console.log("Create expense: " + JSON.stringify(newExpense));
helper.createExpense(component, newExpense);
}
}
The mdn documentation for Array.prototype.reduce has a good description and examples on how to use it.
It should take an array of things and return one other thing (can be different type of thing). But you won't find any examples there where side effects are initiated in the reducer function.

how to print a javascript object's elements

i am new to javascript and i currently have an object printed to console when i use the following code:
clickEvents: {
click:function(target) {
console.log(target);
}
}
when i view console i can see the following object:
i am banging my head against a wall to write code that takes the object and prints it to a div using the .append() method. i am extermely new to working with javascript objects, and would appreciate any help trying to tease out an object and/or print the object data.
is events the object name? would i tease out the eventDate using something like events->eventDate?
I've made this over ~15 minutes so it's imperfect; there are types and edge cases surely unaccounted for and the design of the function could be better - not to mention that performing all of this as a giant string and then setting that as HTML is likely bad practice (I'm used to React now, ha!). Regardless, this will iterate over any array or object you pass to it and print it all in a big <ul> recursively.
const targetEl = document.querySelector('.js-target')
if (!targetEl) return
// Small helper functions
const isObj = data => typeof data === 'object' && !Array.isArray(data) && data !== null
const isArr = data => Array.isArray(data)
const dataToHTML = (data, noNode = false) => {
if (isObj(data)) {
const accumulator = Object.entries(data).reduce((acc, set) => acc + `<li><strong>${set[0]}</strong>: ${dataToHTML(set[1], true)}</li>`, '')
return `<ul>${accumulator}</ul>`
}
else if (isArr(data)) {
const accumulator = data.reduce((acc, item) => acc + dataToHTML(item), '')
return `<ul>${accumulator}</ul>`
}
else return noNode ? data : `<li>${data}</li>`
}
const logHTML = dataToHTML(exampleData)
targetEl.innerHTML = logHTML
Assuming that your data/variable is named exampleData.
Any questions pop them in the comments :-)
I'm not sure if you have a div that you want to append to already, but you would do something like this ->
document.getElementById("toBeAppendedTo").innerHTML = target.events[0].eventDate; where toBeAppendedTo is the id of the div you're trying to add this text to.
append() is a jquery function, not a javascript function.
That won't have any formatting and will just be the string value 07-28-2017 in a div.

How to manage state without using Subject or imperative manipulation in a simple RxJS example?

I have been experimenting with RxJS for two weeks now, and although I love it in principle I just cannot seem to find and implement the correct pattern for managing state. All articles and questions appear to agree:
Subject should be avoided where possible in favor of just pushing state through via transformations;
.getValue() should be deprecated entirely; and
.do should perhaps be avoided except for DOM manipulation?
The problem with all such suggestions is that none of the literature appears to directly say what you should be using instead, besides "you'll learn the Rx way and stop using Subject".
But I cannot find a direct example anywhere that specifically indicates the correct way to perform both additions and removals to a single stream/object, as the consequence of multiple other stream inputs, in a stateless and functional manner.
Before I get pointed in the same directions again, problems with uncovered literature are:
The Introduction to Reactive Programming You've been missing: great starting text, but does not specifically address these questions.
The TODO example for RxJS comes with React and involves explicit manipulation of Subjects as proxies for React Stores.
http://blog.edanschwartz.com/2015/09/18/dead-simple-rxjs-todo-list/ : explicitly uses a state object for addition and removal of items.
My perhaps 10th rewrite of the standard TODO follows - My prior iterations covered include:
starting with a mutable 'items' array - bad as state is explicit and imperatively managed
using scan to concatenate new items to an addedItems$ stream, then branching another stream where the removed items were deleted - bad as the addedItems$ stream would grow indefinitely.
discovering BehaviorSubjectand using that - seemed bad since for each new updatedList$.next() emission, it requires the previous value to iterate, meaning that Subject.getValue() is essential.
trying to stream the result of the inputEnter$ addition events into filtered removal events - but then every new stream creates a new list, and then feeding that into the toggleItem$ and toggleAll$ streams means that each new stream is dependent on the previous, and so causing one of the 4 actions (add, remove, toggle item or toggle all) requires the whole chain to be unnecessarily run through again.
Now I have come full circle, where I am back to using both Subject (and just how is it supposed to be successively iterated upon in any way without using getValue()?) and do, as show below. Myself and my colleague agree this is the clearest way, yet it of course seems the least reactive and most imperative. Any clear suggestions on the correct way for this would be much appreciated!
import Rx from 'rxjs/Rx';
import h from 'virtual-dom/h';
import diff from 'virtual-dom/diff';
import patch from 'virtual-dom/patch';
const todoListContainer = document.querySelector('#todo-items-container');
const newTodoInput = document.querySelector('#new-todo');
const todoMain = document.querySelector('#main');
const todoFooter = document.querySelector('#footer');
const inputToggleAll = document.querySelector('#toggle-all');
const ENTER_KEY = 13;
// INTENTS
const inputEnter$ = Rx.Observable.fromEvent(newTodoInput, 'keyup')
.filter(event => event.keyCode === ENTER_KEY)
.map(event => event.target.value)
.filter(value => value.trim().length)
.map(value => {
return { label: value, completed: false };
});
const inputItemClick$ = Rx.Observable.fromEvent(todoListContainer, 'click');
const inputToggleAll$ = Rx.Observable.fromEvent(inputToggleAll, 'click')
.map(event => event.target.checked);
const inputToggleItem$ = inputItemClick$
.filter(event => event.target.classList.contains('toggle'))
.map((event) => {
return {
label: event.target.nextElementSibling.innerText.trim(),
completed: event.target.checked,
};
})
const inputDoubleClick$ = Rx.Observable.fromEvent(todoListContainer, 'dblclick')
.filter(event => event.target.tagName === 'LABEL')
.do((event) => {
event.target.parentElement.classList.toggle('editing');
})
.map(event => event.target.innerText.trim());
const inputClickDelete$ = inputItemClick$
.filter(event => event.target.classList.contains('destroy'))
.map((event) => {
return { label: event.target.previousElementSibling.innerText.trim(), completed: false };
});
const list$ = new Rx.BehaviorSubject([]);
// MODEL / OPERATIONS
const addItem$ = inputEnter$
.do((item) => {
inputToggleAll.checked = false;
list$.next(list$.getValue().concat(item));
});
const removeItem$ = inputClickDelete$
.do((removeItem) => {
list$.next(list$.getValue().filter(item => item.label !== removeItem.label));
});
const toggleAll$ = inputToggleAll$
.do((allComplete) => {
list$.next(toggleAllComplete(list$.getValue(), allComplete));
});
function toggleAllComplete(arr, allComplete) {
inputToggleAll.checked = allComplete;
return arr.map((item) =>
({ label: item.label, completed: allComplete }));
}
const toggleItem$ = inputToggleItem$
.do((toggleItem) => {
let allComplete = toggleItem.completed;
let noneComplete = !toggleItem.completed;
const list = list$.getValue().map(item => {
if (item.label === toggleItem.label) {
item.completed = toggleItem.completed;
}
if (allComplete && !item.completed) {
allComplete = false;
}
if (noneComplete && item.completed) {
noneComplete = false;
}
return item;
});
if (allComplete) {
list$.next(toggleAllComplete(list, true));
return;
}
if (noneComplete) {
list$.next(toggleAllComplete(list, false));
return;
}
list$.next(list);
});
// subscribe to all the events that cause the proxy list$ subject array to be updated
Rx.Observable.merge(addItem$, removeItem$, toggleAll$, toggleItem$).subscribe();
list$.subscribe((list) => {
// DOM side-effects based on list size
todoFooter.style.visibility = todoMain.style.visibility =
(list.length) ? 'visible' : 'hidden';
newTodoInput.value = '';
});
// RENDERING
const tree$ = list$
.map(newList => renderList(newList));
const patches$ = tree$
.bufferCount(2, 1)
.map(([oldTree, newTree]) => diff(oldTree, newTree));
const todoList$ = patches$.startWith(document.querySelector('#todo-list'))
.scan((rootNode, patches) => patch(rootNode, patches));
todoList$.subscribe();
function renderList(arr, allComplete) {
return h('ul#todo-list', arr.map(val =>
h('li', {
className: (val.completed) ? 'completed' : null,
}, [h('input', {
className: 'toggle',
type: 'checkbox',
checked: val.completed,
}), h('label', val.label),
h('button', { className: 'destroy' }),
])));
}
Edit
In relation to #user3743222 very helpful answer, I can see how representing state as an additional input can make a function pure and thus scan is the best way to represent a collection evolving over time, with a snapshot of its previous state up to that point as an additional function parameter.
However, this was already how I approached my second attempt, with addedItems$ being a scanned stream of inputs:
// this list will now grow infinitely, because nothing is ever removed from it at the same time as concatenation?
const listWithItemsAdded$ = inputEnter$
.startWith([])
.scan((list, addItem) => list.concat(addItem));
const listWithItemsAddedAndRemoved$ = inputClickDelete$.withLatestFrom(listWithItemsAdded$)
.scan((list, removeItem) => list.filter(item => item !== removeItem));
// Now I have to always work from the previous list, to get the incorporated amendments...
const listWithItemsAddedAndRemovedAndToggled$ = inputToggleItem$.withLatestFrom(listWithItemsAddedAndRemoved$)
.map((item, list) => {
if (item.checked === true) {
//etc
}
})
// ... and have the event triggering a bunch of previous inputs it may have nothing to do with.
// and so if I have 400 inputs it appears at this stage to still run all the previous functions every time -any- input
// changes, even if I just want to change one small part of state
const n$ = nminus1$.scan...
The obvious solution would be to just have items = [], and manipulate it directly, or const items = new BehaviorSubject([]) - but then the only way to iterate on it appears to be using getValue to expose the previous state, which Andre Stalz (CycleJS) has commented on in the RxJS issues as something that shouldn't really be exposed (but again, if not, then how is it usable?).
I guess I just had an idea that with streams, you weren't supposed to use Subjects or represent anything via a state 'meatball', and in the first answer I'm not sure how this doesn't introduce mass chained streams which are orphaned/grow infinitely/have to build on each other in exact sequence.
I think you already found a good example with : http://jsbin.com/redeko/edit?js,output.
You take issue with the fact that this implementation
explicitly uses a state object for addition and removal of items.
However, thas is exactly the good practice you are looking for. If you rename that state object viewModel for example, it might be more apparent to you.
So what is state?
There will be other definitions but I like to think of state as follows:
given f an impure function, i.e. output = f(input), such that you can have different outputs for the same input, the state associated to that function (when it exists) is the extra variable such that f(input) = output = g(input, state) holds and g is a pure function.
So if the function here is to match an object representing a user input, to an array of todo, and if I click add on a todo list with already have 2 todos, the output will be 3 todos. If I do the same (same input) on a todo list with only one todo, the output will be 2 todos. So same input, different outputs.
The state here that allows to transform that function into a pure function is the current value of the todo array. So my input becomes an add click, AND the current todo array, passed through a function g which give a new todo array with a new todo list. That function g is pure. So f is implemented in a stateless way by making its previously hidden state explicit in g.
And that fits well with functional programming which revolves around composing pure functions.
Rxjs operators
scan
So when it comes to state management, with RxJS or else, a good practice is to make state explicit to manipulate it.
If you turn the output = g(input, state) into a stream, you get On+1 = g(In+1, Sn) and that's exactly what the scan operator does.
expand
Another operator which generalizes scan is expand, but so far I had very little use of that operator. scan generally does the trick.
Sorry for the long and mathy answer. It took me a while to get around those concepts and that's the way I made them understandable for me. Hopefully it works for you too.

Categories

Resources