MobX works like a spreadsheet, everything is derived from state. Even effects with things like autorun.
The problem for me is that it's unclear when to run an effect based on some event firing, rather than based on a state-change (using autorun or something like that).
For instance let's say I need to fetch some milk (an action that causes an HTTP request - hence an effect) after 3 specific conditions are met (derived from state). I can use when to run this side-effect:
when(
() => {
if (
self.isOnConfirmationPage &&
paymentStore.successfullySubscribed &&
calendarStore.isFriday
) {
return true;
}
return false;
},
() => self.fetchMilk(),
);
A new requirement comes in asking me to add some logging. Specifically I need to call a track() service (another HTTP request hence another effect), but this track() service depends on the data returned by the fetchMilk() service.
Now I could simply add .then() to my fetchMilk:
self.fetchMilk().then(milkResponse => self.track(
milkResponse.user,
milkResponse.order
))
Going back to the title of my question, this to me is "reacting based on an event" - the event in this case being the response by the fetchMilk() service.
What if I simply react based on the state-change rather than the event?
This means track() needs to be also placed in a reaction, and since it depends on the response of fetchMilk() I can simply store this in a MobX store and react upon it:
when(
() => {
if (
self.milk.user &&
self.milk.order
) {
return true;
}
return false;
},
() => self.track(self.milk.user, self.milk.order),
);
Note that instead of using a "promise-based" flow for handling async behavior, I'm simply reacting based on values changing.
My question: which of these two options should I use? What are the benefits/cons of each approach? Is it safe to model async behavior using the 2nd approach?
I would go for the promise.then() version, simply because it is easier to follow the code flow.
There is no rule that says that everything in the app must be set up so that all code is driven by mobx actions and reactions.
When you want a change in the app to be broadcasted to the world, then you enter the mobx world.
When you think about it, mobx is just like an event dispatcher, but the event dispatch code (subscriptions and notifications) are hidden behind mobx magic.
So the best option in my experience is to do all the async work you need to do, and then notify mobx of the changes.
If you take a look at the official documentation it has an example of using asynchronous code, which does exactly that.
Related
backstory: In chessboardjs you load the board with callback functions for specific events. Chess mechanic that gives me problem is the promotion. I need to hold my callback, prompt a user for a piece he wants to promote to, check if it is a valid move and return('snapback') or execute the move.
Stupid solution: If I prompt with classic
promoPiece = prompt();
everything works, but it looks kinda ugly.
Failed solution: But when I try to create a promise that attaches a callback to my overlay element, to get user click on specific piece I fail(i need to make function async to use promises). I try to get the piece as
async function onDrop(args)
{
..code..
promoPiece = await promoPrompt();
..code..
if(invalid())
{return 'snapback'}
}
and the promotion mechanic works, but the return snapback statement does not work because now the promise is returned instead of a string and it is not accepted with the chessboardjs...(I assumed, I did not dig into source)
Question: 1)How does prompt differ from my solution? 2)How does it work? 3)How can i create a custom promotion handler for a synchronous callback? 4)Can i use promoPrompt.then(myResolve) syntax without making onDrop() async?
It is synchronous
Natively (i.e. the code is part of the browser and exposed as an API via the prompt function)
You can't (unless it is just a wrapper around prompt but then you get the native look and feel that you dislike)
Well, yes, but the value will won't be available until that future so you can't return it to the calling function
If you want a custom UI here, then the calling function needs to be able to handle it asynchronously.
I have a screen with some choices on. If you select the choice it sets state of the data. I then have a confirm button. if the user hits confirm I make an async call to get some extra data. I want to wait for this to happen before opening the modal as I need to present that extra data in my modal.
before hooks I would use setState and do something like:
this.setState({data: myData}, () => this.openModal()) as this would reliably set the state then open the modal. all the answers online seem to suggest using useEffect but it seems dodgy to do this:
useEffect(() => {
if (data) {
setModalOpen(true)
}
}, [data, setData])
I don't want my modal potentially randomly opening at different points. plus it seems better to have the code living in the same place I set state. it makes sense to be there. not some random useEffect
any suggestions how this can be achieved?
(one other solution I can think of is making the API call on every choice select, rather than before confirm) however, this could lead to a lot of unnecessary API calls so I'd rather not go down that route.
Using useEffect() is correct, I also encountered this issue when trying to do a callback on setState with hooks.
Like you said: this.setState({data: myData}, () => this.openModal()) was possible before, but now when trying this with hooks the console displays the error:
Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
So useEffect() seems the way to go.
You should use useEffect() as a callback after the state is correctly setted if you would like to do something with the state like validation.
useEffect(() => {
// fetch on load
axios.get("https://randomuser.me/api/").then((response) => {
setPerson(response.data.results[0]);
});
}, []);
useEffect(() => {
// do some validation perhaps
if (person !== null) {
if (person.name.first && person.name.last) {
setModal(true);
} else {
setModal(false);
}
}
}, [person]); // add person in dependency list
As suggested in the comments, you could also do setModal() when the async data has arrived (using .then() or await).
Some example code using random user generator API and axios for fetching.
useEffect(() => {
// fetch on load
axios.get("https://randomuser.me/api/").then((response) => {
setPerson(response.data.results[0]);
setModal(true); // set modal visibility
});
}, []);
I am using angular 8.
There is one auto-complete input and if it's value changes I have to make API call and load new suggestions for this input.
//In Template
<autocomplate [suggestions]="suggestions" (filterChange)="filterChange($event)"></autocomplate>
//In Component
filterChange(e) {
console.log(e)
this.loadSubscriptions(e ? { 'filterItem.name': e } : {})
}
loadSubscriptions(params) {
if (this.suggestionsSubscriber) this.suggestionsSubscriber.unsubscribe()
this.suggestionsSubscriber = this.suggestionsService.loadData(params).subscribe(
data => this.suggestions = data
})
}
Everything works fine, but the problem is when user types fast application makes to many requests.
Can I somehow delay requests if user types fast? for example, while the user is typing don't make API calls on every change, and if the user stops typing then make API call.
Or if you have a better way to solve this problem, please share.
Use RXJS denounceTime operator. Simply chain it to your Observable.
Whenever debounceTime receives an event, it waits a designated amount of time to see if another event comes down the pipe. If it does, it restarts its timer. When enough time has passed without another event streaming in, it emits the latest event.
I would suggest you to use throttle or debounce. You can write your own implementation for those or use library such as lodash.
Debounce using latest Rxjs can be a work around. Please see below for implementation.
Angular and debounce
I also had a same problem, so i put my code inside setTimeout as below
filterChange(e) {
console.log(e)
setTimeout(()=>{
this.loadSubscriptions(e ? { 'filterItem.name': e } : {})
},2000);
}
Now if you type very fast then it will not call the loadSubscriptions at that time. it will call after 2 sec.
You can configure the time according to your choice.
I hope This will helps you.
If I have a service containing an Observable and I have the following code...
this.collapse.newChild.subscribe(name => {
if (name === "helpArea") {
this.collapse.process("helpArea", true);
}
// I would like to detach now
});
Is it possible to detach without canceling the original subscription? If I call unsubscribe then when I call this.collapse.newChild.next("thing") it throws an error Error: object unsubscribed.
So I don't think unsubscribe is what I am looking for, so is there a way to detach a single observer?
As a work around for now I am using...
this.collapse.newChild.map(name => name === "helpArea").first().subscribe(...)
Peter answer is correct from a design/practice PoV, but beware also that you get this error because you unsubscribe the subject.
What you want to stop is the subscription instead:
var subscription = this.collapse.newChild.subscribe(...)
// NOT this
// this.collapse.newChild.unsubscribe()
// this
subscription.unsuscribe()
This will kill/stop not the subject, but only your current subscription to it.
I don't think your "workaround" is at all bad. It even seems idiomatic: the way that Rx wants you to do it. But you can write it a bit shorter, using the form of first that takes a predicate.
this.collapse.newChild
.first(name => name === "helpArea")
.subscribe(...);
In general, the mindset to get into with Rx is to as much as possible use combinators to manipulate streams to produce exactly the values that you need, and then subscribe with a simple handler that applies the final, effectful, step: updating the UI, calling a service etc. This will lead to cleaner, more expressive code, that is easier to read and easier to re-use.
Another nice thing about this approach for your example is that there is no need to unsubscribe. RX will do that for you after the first value because it knows there won't be more. So it's much cleaner than doing it yourself.
Calling unsubscribe() on a Subscription object unsubscribes only this particular observer. So a question is how did you unsubscribed at the first place because using first() sends complete notification which causes unsubscription anyway.
Btw, you can always unsubscribe inside your own subscriber's callback:
var subscription = this.collapse.newChild.subscribe(name => {
if (name === "helpArea") {
this.collapse.process("helpArea", true);
}
subscription.unsubscribe();
});
Scenario :- a json with checked & unchecked item count needs to be sent to a common function which in turn does some basic validation or shows error. There are different function each handle by different css/js team which call this function & depending on the common function result (true/false) each team/module that provided the json with check/uncheck status does the relevant action. Thus both common function (say getcount) & every independent team calling this function rely on the result of common function manipulate the dom/css independently & the common function too manipulate dom independently.
Instead of conventional way of calling function (say getCount({"chk" : 2 , "unchk" : 4})) i am looking for pusblish/subcribe method which is more cleaner as in Jquery & easy to convey to all as only topic name & contract/json needs to describe (also less if/else clause). Since common function is subscriber & when every independent function publishes the resultant chk/unchek json the common function can easily do the manipulation using the publish/subscribe approach but the independent method has to do counter action as well which is only possible if subscribe function can send the result. I know javascript is asynchronous also i understand that common method can publish (say "resultOfGetCount") which every independent function/module can listen to & do the action but is there any way in either in plain javascript or jquery where in on which subscriber can send the publisher the result in a way similar to $.ajax where in the callback function is called once server call is complete.
Looking for the best approach for such scenario.
Here's a simple publish subscribe model in jQuery from this blog post
var exampleHandle = function(){
//do stuff when topic is published
...
}
function subscribe(topic,handle){
$("#subscription").bind(topic,handle);
}
function publish(topic,params){
$("#subscription").trigger(topic,params)
}
function unsubscribe(topic,handle){
$("#subscription").unbind(topic,handle);
}
where you can use a simple string as a topic to subscribe or unsubscribe to the topic, and attach the handle as a callback function. The handle can also be used to unsubscribe from the topic later if necessary.
The method is based on jQuery’s trigger and bind functions. These
allow you to listen for a custom event on an element, and manually
trigger an event on an element. This provides the basic backbone for
the simple subscription model.
If an application element wants to
subscribe to a topic, they bind a handler to the “subscription
element”. This can be a designated element on the page, or just the
window element. You can also of course use different elements for
different subscriptions. Then, when something publishes to that topic,
the handler function will execute.
For publishing, a function can pass
a topic and parameters to the publish function. This calls jQuery’s
trigger to set off the event topic, passing along the parameters.
These params are passed to the handle function.
If an element wants to
cancel its subscription, they can pass the topic and handle function
to the unsubscribe method. Note that the handle has to be the same
function object that was used to originally subscribe, not a copy or
similar function. You also can use jQuery’s unbind to cancel all
subscriptions to a topic by only specifying the topic without the
handle function.