Redux-toolkit deeply nested flatmapped data not updating state - react-redux

I have deeply nested data, and need to update some deeply nested child. Currently I do it by flatmapping the two upper level lists, then searching in all the possible tasks, and then mutating the task by calling the init function.
const tasks = state.data.flatMap((p) => p.hierarchyLines).flatMap((h) => h?.tasks);
const task = tasks.find((t) => t?.id === payload.id);
task?.init(payload);
task.init(data: any):
this.id = _data["id"];
this.start = _data["start"] ? new Date(_data["start"].toString()) : <any>undefined;
this.deadline = _data["deadline"] ? new Date(_data["deadline"].toString()) : <any>undefined;
...
This does not work, any advice on why it is not updating the state?

If anyone should ever run into a similiar issue. The problem was that immer does not handle classes well.
https://immerjs.github.io/immer/complex-objects
For me the solution was copying the state first, then mutating it, and then returning the copy as described here: https://redux-toolkit.js.org/usage/immer-reducers#immer-usage-patterns
builder.addCase(fetch.fulfilled, (state, { payload }) => {
const stateCopy = _.cloneDeep(state);
stateCopy.loading = false;
const tasks = stateCopy.data.flatMap((p) => p.hierarchyLines).flatMap((h) => h?.tasks);
const task = tasks.find((t) => t?.id === payload.id);
task?.init(payload);
return stateCopy;
});

Related

How to create a method that returns Observable that emits result of 2 Promises that need to be executed one after another?

I asked a question
Is Observable from chained promises equivalent of observables created with from and chained with concatMap?
on totally false premises. It seems that neither of my solutions had nothing to do with my intention.
I created a method that returns Observable and calls 2 methods returning Promise. I tried 2 ways:
public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
return from(this.db.selectionItemInfos.clear().then(() => {
return this.db.selectionItemInfos.bulkAdd(itemInfos);
}));
}
public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
const clear$ = from(this.db.selectionItemInfos.clear());
const bulkAdd$ = from(this.db.selectionItemInfos.bulkAdd(itemInfos));
return clear$.pipe(concatMap(() => bulkAdd$))
}
the use will be:
myService.setItemInfos(itemInfos).subsribe(count => {
console.log(`Cleared the table 1st and then added ${count} new items`);
});
I thought from both versions that:
table clear is execution is finished when bulkAdd starts
when bulkAdd is finished i get the count from that in subscribe
How this should really be done? Or can it be done?
This is (from what I can tell here), how I would do it.
In general, defer (or any higher-order operator) is a better way to create an observable from a promise. Defer lets you take the eager evaluation semantics of promises and turn them into the lazy evaluation semantics of observables.
Then all the usual observable operators and such will function as expected.
public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
const clear$ = defer(() => this.db.selectionItemInfos.clear());
const bulkAdd$ = defer(() => this.db.selectionItemInfos.bulkAdd(itemInfos));
return concat(clear$, bulkAdd$);
}
Update 1:
So I think I might know what you're after. This isn't really idiomatic RxJS since it's such an interleaving mix of declarative, imperative style of code. Even so, this should work? I haven't tested it fully, but some tinkering and I think this should do what you're after.
There's most assuredly a better way to accomplish the same thing, but without seeing the bigger picture of what you're after, it's hard to say.
interface Tagged<T> {
payload: T,
tag: number
}
class abitraryClass{
private setItemInfoSub: Subject<Tagged<IItemInfo[]>>;
private processItemInfo: Observable<Tagged<number>>;
private itemInfoTag = 0;
constructor(){
this.setItemInfoSub = new Subject<Tagged<IItemInfo[]>>();
this.processItemInfo = this.setItemInfoSub.pipe(
concatMap(({tag, payload: itemInfos}) => this.db.selectionItemInfos.clear().pipe(
ignoreElements(),
concatWith(defer(() => this.db.selectionItemInfos.bulkAdd(itemInfos))),
map(response => ({
payload: response,
tag
}))
)),
shareReplay(1)
);
// Make the processing pipeline live at all times.
this.processItemInfo.subscribe();
}
public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
const myTag = this.itemInfoTag++;
this.setItemInfoSub.next({
payload: itemInfos,
tag: myTag
});
return this.processItemInfo.pipe(
filter(({tag}) => tag == myTag),
map(({payload}) => payload)
);
}
}

rxjs why does behaviour subject when piped triggered with next()

I have read the tutorial from this link https://magarcia.io/2019/02/18/bloc-pattern-with-react-hooks
and i just dont understand how the search query to the API is triggered when _query.next is called with new search terms
see below code.
export class SearchBloc {
private _results$: Observable<string[]>;
private _query$ = new BehaviorSubject<string>('');
constructor(private api: API) {
**this._results$ = this._query$.pipe(
switchMap((query) => {
return observableFrom(this.api.search(query));
})
);**
get results$(): Observable<string[]> {
return this._results$;
}
}
const SearchInput = () => {
const searchBloc = useContext(SearchContext);
const [query, setQuery] = useState('');
useEffect(() => {
searchBloc.query.next(query);
}, [searchBloc, query]);
return (
<input
type="text"
name="Search"
value={query}
onChange={({ target }) => setQuery(target.value)}
/>
);
};
Assuming that searchblock was put in the context, and during input change the query which is a behaviour subject is assigned a new value with next();
how or why does the api query executes?
I guess I did not understand the line with
this._results$ = this._query$.pipe(
switchMap((query) => {
so maybe the question is, how did the pipe worked? did it create a method callback that will execute when next is called? and what is the assignment to result mean?
anyone that can help me make sense of it is greatly appreaciated.
Consider the following code:
It creates a stream of 5 numbers. Then it creates a second stream which is defined as a stream that has all the same numbers as the first one, only each number is incremented.
const numberStream$ = of(1,2,3,4,5);
const numbersPlus1$ = numberStream$.pipe(
map(v => v + 1)
);
numbersPlus1$.subscribe(console.log);
If you subscribe to numberStream$ you should expect to get 1,2,3,4,5.
If you subscribe to numbersPlus1$ you should expect to get 2,3,4,5,6.
Here we do the same thing with a Subject. Of course, unlike of(1,2,3,4,5), a subject lets you create a stream imperatively. Whenever I call .next on a subject, I'm saying "Make this value the next emission in this subject's stream."
const numberSubject$ = new Subject<number>();
const numbersPlus1$ = numberSubject$.pipe(
map(v => v + 1)
);
numbersPlus1$.subscribe(console.log);
numberSubject$.next(1);
numberSubject$.next(2);
numberSubject$.next(3);
numberSubject$.next(4);
numberSubject$.next(5);

rxjs - how can you create another observable from an observable but ignore its output in the parent observable?

I have a situation where I have an observable, and for each emitted item, I want to create another observable, but ignore that observable's value and instead return the result of the first observable.
For example, if I click a button, I want to track something that happens in another button, only when the first button is toggled on.
I can do this now, sort of, with a hack, by taking the output of the child observable and piping it to a mapTo with the parent's value. You can see it in this code, which can be played with in a code sandbox:
import { fromEvent, from } from "rxjs";
import { mapTo, switchMap, tap, scan } from "rxjs/operators";
const buttonA = document.getElementById("a");
const buttonB = document.getElementById("b");
const textA = document.querySelector('#texta');
const textB = document.querySelector('#textb');
fromEvent(buttonA, 'click').pipe(
// this toggles active or not.
scan((active) => !active, false),
switchMap(active => {
if (active) {
const buttonBClicks$ = fromEvent(buttonB, 'click');
// here we can observe button b clicks, when button a is toggled on.
return buttonBClicks$.pipe(
// count the sum of button b clicks since button a was toggled on.
scan((count) => count+1, 0),
tap(buttonBCount => {
textB.value = `button b count ${buttonBCount}`;
}),
// ignore the value of the button b count for the final observable output.
mapTo(active)
)
} else {
textB.value = ``;
return from([active]);
}
})
).subscribe({
next: buttonActive => {
textA.value = `Button a active: ${buttonActive}`
}
});
A couple issues here. In the case that the button is toggled on, the outer observable only receives a value once the button is clicked.
This mapTo use seems hacky.
Any better ways to do this?
It sounds like you don't want the inner observable to actually be a part of the process at all. Are you waiting on it or anything?
If not, you can just do it all as a side effect as follows:
fromEvent(buttonA, 'click').pipe(
scan((active) => !active, false),
tap(active => { if(active) {
fromEvent(buttonB, 'click').pipe(
scan(count => count+1, 0),
tap(buttonBCount => {
textB.value = `button b count ${buttonBCount}`;
})
).subscribe()
}})
).subscribe({
next: buttonActive => {
textA.value = `Button a active: ${buttonActive}`
}
});
Nested subscriptions are considered bad voodoo, so you ca refactor like this to keep your separation of conserns more apparent:
const trackActiveFromButton$ = fromEvent(buttonA, 'click').pipe(
scan((active) => !active, false),
shareReplay(1)
);
trackActiveFromButton$.subscribe({
next: buttonActive => {
textA.value = `Button a active: ${buttonActive}`
}
});
trackActiveFromButton$.pipe(
switchMap(active => active ?
fromEvent(buttonB, 'click').pipe(
scan(count => count+1, 0),
tap(buttonBCount => {
textB.value = `button b count ${buttonBCount}`;
})
) :
EMPTY
)
).subscribe();
Any better ways to do this?
The below may be better depending on your taste. It seems to me your sample code gets a little messy because you have a single observable that is trying to do too many things. And the side-effects are sort of mixed in with the stream behavior logic.
It's totally fine to be use tap() to do side-effect type things, but sometimes it can make it harder to follow. Especially in the above code, since there is a nested observable involved.
Creating separate observables that always emit specific data can make things easier to follow.
If we declare a stream to represent the isActive state and subscribe to that to update textA, and define a counter stream to represent the number of clicks that occurred while isActive = true, using that value to update textB, I think it makes it easier to follow what's going on:
const clicksA$ = fromEvent(buttonA, 'click');
const clicksB$ = fromEvent(buttonB, 'click');
const isActive$ = clicksA$.pipe(
scan(active => !active, false),
startWith(false)
);
const counterB$ = combineLatest([isActive$, clicksB$]).pipe(
scan((count, [isActive]) => isActive ? count + 1 : -1, 0)
);
counterB$.subscribe(
count => textB.value = count === -1 ? '' :`button b count ${count}`
);
isActive$.subscribe(
isActive => textA.value = `Button a active: ${isActive}`
);
To me, having the streams defined separately makes it easier to see the relationship between them, meaning, it's easier to tell when they will emit:
isActive derives from clicksA
counterB derives from clicksB & isActive
Here's a working StackBlitz
Also:
the outer observable only receives a value once the button is clicked
This can be solved using startWith() to emit a default value.

How to share a single Subject source

I'm trying to share a Subject source across multiple functions that filter their actions and do appropriate tasks, the ones that are not filtered should fall trough without modifications.
I've tried merging same source but it doesn't really work the way I need it to...
const source = new Subject()
source.next({ type: 'some type', action: {} })
merge(
source,
source.pipe(filter(...), do something),
source.pipe(filter(...), do something),
source.pipe(filter(...), do something),
source.pipe(filter(...), do something),
).subscribe(...)
In this case I get original source + filtered ones.
I'm expecting to be able provide same source to multiple functions that can filter on types and do async behaviours, rest of the types that were not filtered should fall trough. Hope this is clear enough, or otherwise will try to make a better example. Thanks!
example here
Basically you want one source with actions. Subject is fine way to do this.
Then you want to do some processing on each type of action. You can filter and subscribe to each substream.
const add$ = source.pipe(filter(a => a.type === "add")).subscribe(function onAddAction(a) {});
const remove$ = source.pipe(filter(a => a.type === "remove")).subscribe(function onRemove(a) {});
Or you can prepare substreams and then merge to all processed actions again.
const add$ = source.pipe(filter(a => a.type === "add"), tap(onAdd));
const remove$ = source.pipe(filter(a => a.type === "remove"), tap(onRemove));
const processedAction$ = merge(add$, remove$);
processedAction$.subscribe(logAction);
If you need to do some preprocessing on all actions you can use share or shareReplay. toAction will be called only once per each item.
const subject = new Subject();
const action$ = subject.pipe(map(toAction), share());
const add$ = action$.pipe(filter(isAdd));
...
merge(add$, remove$).subscribe(logAction);
And if you have problems splitting:
function not(predicate) {
return function(item, ...args) {
return !predicate(item, ...args);
}
}
function any(...predicates) {
return function(item, ...args) {
return predicates.some(p => p(item, ...args));
}
}
const a = source.pipe(filter(fa), map(doA));
const b = source.pipe(filter(fb), map(doB));
const c = source.pipe(filter(fc), map(doC));
const rest = source.pipe(filter(not(any(fa, fb, fc)));
merge(a, b, c, rest).subscribe(logAction);

How to combine two list of object in one list with Rxjs

I have 2 differents list of objects and i want to combine them together for exemple:
listObj1 = [{name:'bob 1'}, {name:'bob 2'}]
listObj2 = [{pseudo:'Bob Razowski'}, {pseudo:'sponge bob'}]
result = [
{name:'bob 1', pseudo:'Bob Razowski}
{name:'bob 2', pseudo:'sponge bob'}
]
Can i do that with rxjs and how or if you have a better solution let me know
const characters = [];
const name$ = Observable.from(this.nameList)
.map(item => {
return {'name': item};
})
const pseudo$ = Observable.from(this.pseudoList)
.map(item => {
return {'pseudo': item};
})
Observable.zip(name$, pseudo$).subscribe(result => {
let char= {};
if(result.length > 1) {
char['name'] = result[0];
char['pseudo'] = result[1];
characters.push(char)
}
});
I started something like that but when i see the result, i can do it without rxjs. My question is more if it exists an other operator to do that.
thank
Well, you can do it with RxJS, but there is no obvious reason to do so, looking at your code snippet. One reason to do it reactively would be that you had really long lists and wanted to let the combination happen on (hypothetical) multiple threads. But in JavaScript that's not really practical, so for...of or Array.map are the right choices for this kind of task.
Anyhow, the RxJS solution would look like this:
zip(
from(listOb1),
from(listObj2)
).pipe(
map(([one, two]) => Object.assign({}, one, two)),
toArray()
)
Convert both lists into Observable streams with from, then zip them together and map each pair onto a new object using Object.assign. Collect the objects with toArray and done.

Resources