I have a code that fetches book by its id
const fetchBook = (bookId: number) => {
const title = 'Book' + bookId;
// mimic http request
return timer(200).pipe(mapTo({ bookId, title }));
}
const bookId$ = new Subject<number>();
const book$ = bookId$.pipe(
switchMap(bookId => fetchBook(bookId)),
shareReplay(1)
);
book$.subscribe(book => console.log('book title: ', book.title))
bookId$.next(1);
I have an API method that patches values and returns the updated object:
const patchBook = (bookId: number, newTitle: string) => {
return timer(200).pipe(mapTo({ bookId, title: newTitle }));
}
What should I do to get book$ to emit the new value after I call patchBook(1, 'New Book Title')?
I can declare book$ as Subject explicitly and update it manually. But it will be imperative, not reactive approach.
Upd: The patch is called as a result of user action at any time (or never)
Upd2: Actually book$ can be also changed on server side and my real code looks like this:
const book$ = combineLatest([bookId$, currentBookChangedServerSignal$]).pipe...
The same thing you did to transform a bookId into a Book, you can use to transform a Book into a patchBook.
const book$ = bookId$.pipe(
switchMap(bookId => fetchBook(bookId)),
mergeMap(({bookId, title}) => patchBook(bookId, title)),
shareReplay(1)
);
Update:
patch is not always called
There are many ways this could be done and the "best" way really depends on how you've architected your system.
Lets say you dynamically create a button that the user clicks and this triggers an update event.
const patchBtn = document.createElement("button");
const patchBook$ = fromEvent(patchBtn, 'click').pipe(
switchMap(_ => patchBook(bookId, title))
);
const basicBook$ = bookId$.pipe(
switchMap(bookId => fetchBook(bookId))
);
const book$ = merge(patchBook$, basicBook$).pipe(
shareReplay(1)
);
You probably want your fromEvent events to emit some data rather then hard-coding (bookId, title) into the stream from a click, but you get the idea. That's just one of many ways to get the job done.
And of course, it should almost always be possible (and desirable) to remove bookId$, and replace it with a more reactive-style mechanism that hooks declarativly into whatever/wherever the ID's come from in the first place.
You can declare a fetchBook$ observable, and a patchBook$ subject. Then your book$ observable can be a merge of the two.
const patchBook = (bookId: number, newTitle: string) => {
return timer(200).pipe(
mapTo({ bookId, title: newTitle }),
tap(newBook=>this.patchBook$.next(newBook))
);
}
const bookId$ = new Subject<number>();
const fetchBook$ = bookId$.pipe(
switchMap(bookId => fetchBook(bookId)),
shareReplay(1)
);
const patchBook$ = Subject<{ bookId: number, newTitle: string}>();
const book$ = merge(fetchBook$, patchBook$);
book$.subscribe(book => console.log('book title: ', book.title))
bookId$.next(1);
patchBook(2, 'Moby Dick');
Related
Learning redux/react-redux, I'm using useSelector with inner selector function in a separate file. It's working perfectly but I have question about best practices.
Assuming that I have a state with 3 entries (firstname, lastname, email), is it better to :
1. Have a specific selector for each case?
selector.js
export const selectFirstname = (state) => state.currentUser.firstName
export const selectLastname = (state) => state.currentUser.lastName
export const selectEmail = (state) => state.currentUser.email
component.js
const firstName = useSelector(selectFirstname)
const lastName = useSelector(selectLastname )
const email = useSelector(selectEmail)
2. Have a generic selector with param?
selector.js
export const selectItem = (key) => {
return (state) => state.currentUser[key]
}
component.js
const firstName = useSelector(selectItem('firstName'))
const lastName = useSelector(selectItem('lastName'))
const email = useSelector(selectItem('email'))
3. Have a global selector and use it with destructuring in my component?
selector.js
export const selectItem = (state) => state.currentUser
component.jsx
const {firstName, lastName, email} = useSelector(selectItem)
Thank you in advance
No, you shouldn't be doing #3. You should be returning the smallest amounts of data per the docs: https://redux.js.org/tutorials/fundamentals/part-5-ui-react#using-multiple-selectors-in-a-component
So 1 or 2 is your best option. Also, anytime currentUser changes the whole thing will re-render and if you are only using 3 of the values, why re-render the component when the values have not changed.
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);
I have the following code:
const fetchBook = (bookId: number) => {
const title = 'Book' + bookId;
console.log('fetch book:', title)
// mimic http request
return timer(200).pipe(mapTo({ bookId, title }));
}
const bookId$ = new Subject<number>();
const book$ = bookId$.pipe(
mergeMap(bookId => fetchBook(bookId))
);
book$.subscribe(book => console.log('subscriber1: ', book.title))
book$.subscribe(book => console.log('subscriber2: ', book.title))
bookId$.next(1);
console output:
fetch book: Book1
fetch book: Book1 <--- called second time
subscriber1: Book1
subscriber2: Book1
What is the best practice to prevent multiple fetches in RxJS?
Upd: I don't use Angular
Upd2: The problem is caused by multiple subscribers
Add shareReplay(1) after your mergeMap(). This will ensure late subscribers will get the last emitted value from the source observable.
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);
Maybe this is not necessarily a reselect question
const makeSelectError = () => createSelector(
selectGlobal,
(globalState) => globalState.get('error')
);
and in reselect we use
const mapStateToProps = createStructuredSelector({
error: makeSelectError(),
});
why can't we use like below?
const makeSelectError = createSelector(
selectGlobal,
(globalState) => globalState.get('error')
);
and use like below in reselect
const mapStateToProps = createStructuredSelector({
error: makeSelectError,
});
Are there any issues/disadvantages with my code, or is that a standard practice?
Not only is the second way valid and correct it has other advantages.
With the first snippet you provide:
const makeSelectError = () => createSelector(
selectGlobal,
(globalState) => globalState.get('error')
);
makeSelectError is a factory function in that every time it is called it is returning a new and unique selector.
This means that every time a simple mapStateToProps function is called a new selector will be made and the result of the selector will be computed again.
This means that you will be losing the key benefit of reselect that is memoization.
So for simple cases you could just do the following:
const getSomePieceOfState = state => state.someArea.someDetail;
const getAnotherPieceOfState = state => state.anotherArea.anotherItem;
const getSomeCombinedState = createSelector(
getSomePieceOfState,
getAnotherPieceOfState,
(somePiece, anotherPiece) => somePiece + anotherPiece
);
const mapStateToProps = state => ({
someProp: getSomeCombinedState(state)
});
Note. it's common to prefix the name of selectors with get and to prefix the name of a selector factory (a function that returns a selector) with makeGet.
Some times creating a selector factory is necessary though if you wan't to make a selector that is dependant on a property that is not in the state.
You can read more about that here Accessing React Props in Selectors
You're doing it absolutely right in the second example. This is a standard practice.
There's no need to do wrap makeSelectError to another function.