How to maintain state in Mock Service Worker - javascript

I have msw setup to mock a GET and POST endpoint by returning a static constant object.
[
rest.get("/something", (r, w, c) =>
w(
c.body({ ... })
)
),
rest.post("/something", (r, w, c) =>
w(
c.body({ status: "ok", entity: ... }))
)
]
Is there a way to maintain state such that a subsequent GET will return whatever I POSTed? Not sure if it's a proper use case for msw since this is basically a server.

Related

Angular rxjs data not updating correctly when using share , map & filter

Based on searchCriteria collected from a form, I have a method that return observable of talents.
getTalents(searchCriteria) {
return this._allUsers$.pipe(
tap((talents) => console.log(talents)),
map((talents: any) => {
let filtered = talents.filter(
(t) =>
(t.userType === 'talent' || t.userType === 'both') &&
this.matchOccupationOrSkill(
searchCriteria.occupation,
t.occupation,
t.skills
)
);
if (searchCriteria.loggedInUserId) {
filtered = filtered.filter(
(t) => t.id !== searchCriteria.loggedInUserId
);
}
return filtered;
})
);
}
where _allUsers$ is a shared observable,
this._allUsers$ = firestore.collection<any>('users').valueChanges().pipe(share());
I am calling getTalents inside ngOnInit method of a route component TalentSearchResult.
ngOnInit(): void {
this.subs.add(
this.route.queryParams.subscribe(params => {
if (params.action==='homesearch' || params.action==='jobsearch'){
this.criteria = {
occupation:params.occupation,
industry:params.industry,
loggedInUserId:params.loggedInUserId,
}
this.dataService.getTalents(this.criteria).pipe(take(1)).subscribe(talents => {
this.count = talents.length;
if (this.count >0){
this.Talents = talents.sort((p1,p2)=> p2.createdate-p1.createdate);
}
})
}
})
)
}
Search works for the first time, then when I go back (using brower back button) and search with a new criteria, it routed correctly to the TalentSearchResult component, however, stale list is being returned. I debugged and found that line 3 in the first code section above, i.e. tap((talents) => console.log(talents)) already logs in the stale value, even before the pipe/map/filter is executed !
Please advice
If you are using _allUsers$ or dataService.getTalents() elsewhere in the app, then you should replace share() with shareReplay(1). Using share() allows multiple subscribers to an observable, but any late subscribers won't get any data until .valueChanges() emits a new value. Using shareReplay(1) will allow late subscribers to immediately get its last cached value.
If you're certain this is the only component subscribing to these observables, then it could be that your component isn't closing subscriptions properly. This may be caused by the nested subscriptions (an anti-pattern) you have inside your ngOnInit() event. I would recommend changing this logic to utilize operators:
this.subs.add(
this.route.queryParams.pipe(
filter(({ action }) => ['homesearch', 'jobsearch'].includes(action)),
map(({ occupation, industry, loggedInUserId }) => ({ occupation, industry, loggedInUserId })),
switchMap(criteria =>
this.dataService.getTalents(criteria).pipe(take(1))
),
filter(talents => !!talents.length),
tap(talents => this.Talents = talents.sort((p1, p2) => p2.createdate - p1.createdate))
).subscribe()
);

Modify Observable with another Observable and return the initial Observable

I want to return a ClientDetails object with a loaded image.
So retrieve an Observable, and modify the value with another Observable and return the whole Observable.
I hope the code below indicates what I am trying to do, but I know it can be done much cleaner using RxJS operators. Anyone know how to?
interface ClientDetails {
team: Member[];
}
interface Member {
id: number;
image: string;
}
this.clientDetails$ = this.clientService.getClientDetails().subscribe((details: ClientDetails) => {
details.team.forEach(member => {
this.imageService.getImage(member.id).subscribe((image: string) => {
member.image = image
}
}
}
You're right in assuming RxJS operators would make it more elegant. At the moment the variable this.clientDetails$ doesn't hold an observable and it wouldn't work as you'd expect it to.
Instead you could use higher order mapping operator switchMap to switch from one observable to another (it's better to avoid nested subscriptions in general) and forkJoin function to trigger multiple observables in parallel. You could also use JS destructing and RxJS map operator to return the object with all it's contents.
Try the following
this.clientDetails$ = this.clientService.getClientDetails().pipe(
switchMap((details: ClientDetails) =>
forkJoin(
details.team.map(member =>
this.imageService.getImage(member.id).pipe(
map(image: string => ({...member, member.image: image}))
)
)
).pipe(
map((team: any) => ({...details, details.team: team}))
)
);
);
Note: I didn't test the code. Please check if the object returned is what you actually require.
Try
this.clientDetails$ = this.clientService.getClientDetails().pipe(
switchMap((details: ClientDetails) => {
const images$: Observable<string[]> = forkJoin(
details.team.map(member => this.imageService.getImage(member.id))
);
return forkJoin([of(details), images$]);
}),
map(([details, images]) => {
return {
team: _.zipWith(details.team, images, (d, m) => d.image = m) // zipWith = lodash function
};
}),
).subscribe((details: ClientDetails) => console.log(details));

Is there a recommended way to run setup in a React component but only after several conditions have been met?

When my component mounts it sends out several requests to an API for data. There is a method in my component that should run only when certain requests have been met, and only once.
I’m already using getDerivedStateFromProps to run other methods that aren’t so needy, but I’m not sure it’s right since the idea there is to compare new prop values against previous state. I have 4 different props I need check to ensure their previous state was null and the new prop has value.
One idea I had was to have an entry in state that looks something like this:
this.state = {
conditions: {
conditionA: false,
conditionB: false,
conditionC: false,
conditionD: false
}
}
Every time a prop has value, update its related item in state:
this.state = {
conditions: {
conditionA: false,
conditionB: false,
conditionC: true,
conditionD: false
}
}
Then once all are true, fire up the method. But is there a better way to do this? Is there a recommended way to ensure several props have value before running a method?
Javascript has a Promise.all that you can use to make sure all your Api requests have been completed and then take an action.
The .then() callback to Promise.all is passed an array that contains the response from all API calls and is triggered when all API calls have succeeded. Even if one fails the .catch callback is triggered
componentDidMount() {
const req = urls.map((url) => { // make API requests
return fetch(url);
});
Promise.all(req).then(res => {
// make the function call here
})
}
I would use componentDidUpdate:
componentDidUpdate(prevProps) {
// check your props values meet your required condition here
if(!prevProps.condition1 && this.props.condition1 === true && ...) {
callsomefunction()
}
}
Assuming these API requests use a technology (like fetch) that returns promises (if not, wrap whatever you are using in something that returns promises), the fairly standard way to do this would be to do this in componentDidMount (the docs call out network requests as one use case for it):
Start the requests in parallel
Use Promise.all to wait for them all to complete (or for one of them to fail)
Call your method that needs the results from a fulfillment handler on the promise from Promise.all
For instance:
componentDidMount() {
Promise.all([
getThingA(),
getThingB(),
getThingC(),
getThingD(),
])
.then(([a, b, c, d]) => {
this.yourMethod(a, b, c, d);
})
.catch(error => {
// Set error state
});
}
(I assume your method eventually calls setState.)
Live Example:
const getThing = label => new Promise(
resolve => setTimeout(resolve, Math.random() * 800, `Value for ${label}`)
);
class Example extends React.Component {
constructor(...args) {
super(...args);
this.state = {
a: null,
b: null,
c: null,
c: null
};
}
componentDidMount() {
Promise.all([
getThing("a"),
getThing("b"),
getThing("c"),
getThing("d"),
])
.then(([a, b, c, d]) => {
this.yourMethod(a, b, c, d);
})
.catch(error => {
// Set error state
});
}
yourMethod(a, b, c, d) {
this.setState({a, b, c, d});
}
render() {
const {a, b, c, d} = this.state;
if (!a || !b || !c || !d) {
return <div>Loading...</div>;
}
return (
<div>a: {a}, b: {b}, c: {c}, d: {d}</div>
);
}
}
ReactDOM.render(
<Example />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
You should use componentDidUpdate hook:
componentDidUpdate(prevProps) {
if(Object.values(prevProps).every(val => val)) {
// do the stuff
}
}
If you want to ignore some state, then you can bypass the state like:
const { ignoredState, ...rest } = prevProps
// now, use Object.values to satisfy with rest
Or, only use single state for them when all api requests are succeeded by using promise as others stated:
// when all promises are done
this.setState({fetched: true})
Now, you can just check fetched state.

Store result of query in local variable and send a confirmation for the same in Angular

My node server is giving me a response for a query result which I want to store in my Angular Service's Local Variable(movie) and pass a confirmation message({"result":true}) to the asking angular component.
redirect(id){
this.movie.getMovie(id).subscribe( confirmation =>{
if(confirmation.result) this.router.navigate(["/movies/movDetails"]);
});
}
This is my angular Component
getMovie(id):Observable<any>{
return this.http.post("http://localhost:3000/getMovie",{ "_id":id }).subscribe(IncomingValue => this.movie = IncomingValue).pipe(map( return {"result":true} ));
}
Service Component
When retrieving data, one often uses a get, not a post. This is what one of my simple gets looks like:
getProducts(): Observable<IProduct[]> {
return this.http.get<IProduct[]>(this.productUrl);
}
Using your code ... you can then use RxJS pipeable operators to perform additional operations:
getMovie(id):Observable<any>{
return this.http.post("http://localhost:3000/getMovie",{ "_id":id })
.pipe(
tap(IncomingValue => this.movie = IncomingValue),
map(() => {return {"result":true}} )
);
}
The first pipeable operator, tap, stores the incoming value in your local property.
The second pipeable operator, map, maps the result as the defined key and value pair.
Hope this helps.

Observable operator for pulling out value for later

I'm using observables with the RXJS library and have a situation where I want to pull out a value and use it at an unspecified time in the future that is not the next operator or the subscribe method.
I am currently passing the value through every operation until I need it. Something that looks like this:
obsveable$.pipe(
ofType('example'),
map((action) => [
action, // <-- passing this through
somethingElse
]),
map(([action, other]) => [
action, // <-- through again
anotherThing
]),
map(([action, other]) => {
// finally use action
}),
);
I could maybe try saving it to a local variable – something like this:
let action;
obsveable$.pipe(
ofType('example'),
map((_action) => action = _action),
map((action) => action.somethingElse),
map((other) => {
// finally use action
})
);
Both of these solutions are suboptimal. The first gets unwieldy when there are a lot of steps and the second is subject to possible order of operations issues (and it's not using the cool reactive approach).
What other options are there?
You can create a closure this way:
obsveable$.pipe(
ofType('example'),
mergeMap(action =>
Observable.of(somethingElse)
.pipe(
map(other => anotherThing),
map(other => {
// use action
})
)
)
);

Categories

Resources