Redux-saga wait and combine multiple action - react-native

I am implementing a log system, when scroll down a list a lot of LIST_ITEM_SHOWN action would be dispatched. Then Saga will call the API to send out the log.
I want to make it wait for e.g. 2 second until no further LIST_ITEM_SHOWN is dispatched, and group the LIST_ITEM_SHOWN actions to one to reduce API call.
Can this be done in Saga level? or can only be managed in component/container level?

Yeah, it's pretty easy to implement in saga:
yield takeLatest('LIST_ITEM_SHOWN', watchListItemShown);
so, taking latest LIST_ITEM_SHOWN action to store to logs, so in case of multiple actions - the previous tasks are cancelled (won't go after delay).
// for simplicity storing logs in private variable, consider using redux or something similar
let logs = [];
function* watchListItemShown(action) {
yield call(logs.push, action.payload);
yield call(delay, 2000);
yield call(saveToApi, logs);
logs = [];
}
So on every LIST_ITEM_SHOWN action you store logs into local variable (you can consider using redux or something similar instead, so you'll do better logs management via reducers). And after 2 seconds delay the actual save is called.

Related

Redux saga: How can i make sure only my saga is able to update a certain state?

I have a mobile app made in React Native, and I've just run into a best practice dilemma i've encountered many times while using Redux/Redux Saga. I would love if i could get someone else's thoughts on this.
For a new piece of functionality i'm implementing, i need to be able to tell how many times the app has been launched. This involves asynchronously retrieving how many times the app was previously launched from the device storage. If there's a new launch happening, i also need to add +1 to the number and store that in the device storage.
This is how i currently do it:
Dispatch appLaunched() action when app launches.
Redux Saga takes event.
Inside Saga: Retrieve how many times app was previously launched (appLaunchCount) from device storage (wait for async to finish).
Add +1 to previous appLaunchCount.
Store new appLaunchCount in device storage (wait for async to finish).
Dispatch put() with new appLaunchCount to reducer.
Update state with new appLaunchCount inside reducer.
My problem with this method is step 6. Technically any part of my app could dispatch a new app launch count to my reducer, with any integer, and the reducer would update the state just the same even though it didn't come from the saga.
My question is this: How can i protect my Reducers/Sagas/Actions so that only my saga can dispatch the action with the current appLaunchCount ?
P.S The only solution i can think of is writing my saga and reducer in the same file, and use private actions that only the saga and reducer can access. I would really hate to have to keep all that code together though.
Private actions aren't really a thing. The store is, by design, a global object. And since actions are just objects with a type property, anyone who can construct an action object of the right type can in principle dispatch an action and kick off your reducer.
What you could do is make the action have a type that makes it obvious that it's meant to be private. For example, maybe the action looks like:
{
type: '__PRIVATE_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__'
// You could tone it down a bit from this :)
}
That of course doesn't make it actually private, but at least if someone wants to use it, it's impossible for them to not realize your intent.
If you wanted to make it more secure, perhaps you could use a symbol as the type, and therefore only anyone with access to the symbol could construct the right action. For example:
const appLaunchCount = Symbol('appLaunchCount');
// action would look like:
{
type: appLaunchCount
}
But then the issue is making sure that symbol stays hidden, and can be accessed only by those who you want to access it. Similar to one of the things you mentioned, if you have the saga/reducer in the same file, then you could make sure that other files couldn't access this symbol; but once you start exporting it it becomes harder to control.

RxJS polling by interval and when manually called

In my Angular app I'm working on notifications and I have an REST API to call for latest user's notifications. I need to call this API on few minutes since it's not really important that user gets notifications in real time (they probably won't even appear that fast). However the idea to refresh notifications on the client side is next:
When user logs in start refreshing notifications - here is the first manual call to start refreshing the API on few minutes
If user leaves app opened or is just navigating through the app then don't change timer and wait for the rest of the time
If user opens subpage where it can perform actions related to notifications and does it, then refresh notifications and reset timer
Refresh notifications until logout
I already have working code for the described procedure, but I'm somehow unsure that it's correct for what I need. Here is the code for performing calls (for manual check there is just a Subject and for stop checking there is a subscription to observable - code below is actually separated, but here is in one place because of readability):
// Subject for manual triggering
this.checkFeed = new Subject<void>();
// Call for refresh in own method
this.checkFeed.next();
// Waiting for manual refresh or triggering it on some interval after it was last triggered
this.feedSub = this.checkFeed.asObservable()
.switchMap(() => Observable.timer(0, this.interval))
.mergeMap(() => this.fetchChanges())
.distinctUntilChanged(this.compareFeed)
.subscribe(res => this.notify(res));
// Unsubscription when logging out
if (this.feedSub) this.feedSub.unsubscribe();
The part which I'm most unsure about is .switchMap(() => Observable.timer(0, this.interval)) since it needs 0 to start right away (which is ok, but still doesn't look correct at all?). So is there any better way to achieve what I described?
I also have another question how to start check for notifications from another observable - which operator should I use. As I mentioned I have call to the Subject's next in own method like this:
refreshFeed(): void {
this.checkFeed.next();
}
So when there is some other observable performing (the action when notifications should be refreshed) I need to call this one. What's the correct way to call void method when other observable has response from API? I was thinking of something like this:
someActionThatCanChangeNotifications(): Observable<any> {
return this.api.get('path/to/endpoint')
.do(() => this.feedService.refreshFeed());
}
Is this ok, or is there also any better way?
Thanks in advance for help!
So basically you have two observable.
One that you call manually:
this.checkFeed
and the interval(let's callit intervalObs):
this.intervalObs = Observable.timer(0, this.interval);
If you see it like this the easyest way is to merge you'r two source stream and then do whatever you want.
var mergedSource = Observable.merge(
this.checkFeed,
this.intervalObs)
subscription = mergedSource.subscribe(this.fetchChanges());
Maybe you need to do some more operation in between but this should give you a more readable alternative.
You can try this working plunker if you want something to play arround https://plnkr.co/edit/n4nNFEMa4YOh2KSjDpSJ?p=preview
From what I can see you've pretty much done it "correctly". As with programming in general, there are many possible (and correct) solutions to a single problem. Personally, I'd do this the same way.
I can give you some commentary on the two points you mentioned too:
.switchMap(() => Observable.timer(0, this.interval))
Observable.timer pretty much an Observable.interval with a custom timeout before the first value. Observable.timer(0, this.interval) is the correct usage.
An alternative could be Observable.just(0).concat(Observable.interval(this.interval)), which returns a value immediately and then starts the interval. I prefer the way you put however; I think it clearly states your intention: "Produce a value after 0 milliseconds, and then an interval of this.interval".
.do(() => this.feedService.refreshFeed())
I'd say this is the totally correct way of doing it. do is meant for side effects, eg. stuff that happening outside the observable.
I can say though, I wouldn't expect someActionThatCanChangeNotifications to kick off a refresh of the feed. When a function returns an observable, I would expect to return an observable that doesn't have any side effects. However, as we live in a non-perfect world, we can't always have what we want.
You can't expect every subscriber to remember to do .do(() => this.feedService.refreshFeed()), instead I'd add a notice in the doc comment for the function: "Note: The returned observable will refresh the feed on every next signal", or something of that kind.

Circular module dependencies between stores

In my react native app that tracks instrument practice I have three stores:
SessionStore
GoalStore
InstrumentStore
The stores each manage one model (Session, Goal, Instrument) and getting/updating the server via a REST api.
The SessionStore listens to actions regarding Sessions (obviously): session.add, session.update. But it also listens to changes to the other stores, to be able to update the Sessions if a Goal or Instrument changes name.
Correspondingly the InstrumentStore listens to Instrument actions, but also to Session actions to update statistics on how many sessions uses a particular instrument.
To be able to not have race conditions, the InstrumentStore will act on the action session.add but wait for the SessionStore to handle the action first (to ensure the Session has been updated in the API). To do this I use dispatcher.waitFor with the SessionStore dispatchToken as a semaphore.
The problem: since all stores use each others dispatchTokens they all have to import each other. This is a circular dependency on modules and leads to strange race conditions. Sometimes one of the stores haven't been constructed when it's included by one of the other stores.
Here are my stores: https://github.com/osirisguitar/GuitarJournalApp/tree/feature/flat-ui/js/stores
Am I using the flux pattern the wrong way?
Addition
This is what I want to happen (in sequence):
Session is updated:
Send updated session to API
Refresh SessionStore
Refresh GoalStore
Refresh InstrumentStore
2, 3 and 4 need to wait for 1 to complete, that's why GoalStore and InstrumentStore need the SessionStore dispatch token.
Goal is update:
Send updated goal to API
Refresh GoalStore
Refresh SessionStore
2 and 3 need to wait for 1, this is why SessionStore needs the GoalStore dispatchToken which introduces the circular dependency.
You have some duplication going on.
All stores will hear all dispatches. That's the beauty of having a single dispatcher. So when you dispatch a sessions.add or sessions.update action, you're hitting three different Stores, and two of them are doing the exact same thing. That's a no-no.
As a rule, each Store's dispatch token should only be responsible for updating that store. So your Goal and Instrument stores should not be updating the SessionsStore. The .refresh and .emit should be happening within the SessionsStore dispatch token only.
EDIT to answer your edited question.
I think your confusion is because you're not recognizing that the dispatcher.register takes in a function as it's argument, and not an object.
Functions, in JS, do not evaluate their contents on declaration. They are evaluated when executed only.
Simple example;
func = function(){ console.log(testVar) } // No error, even though testVar is undefined
func() // ERROR: testVar is undefined
var testVar = 'hey';
func() // log: 'hey';
dispatcher.register takes a function as it's input, and returns an key (in the format ID_#). That key is generated by the dispatcher itself without running the input function. The input function is simply stored for later and run each time a payload is dispatched.
That means that you don't need the internal variables to be defined until your first dispatch. And because you also don't want to dispatch anything until you've created your stores, this becomes a non-issue.
But it also means that the dispatcher, by default, has a sort-of circular dependency against itself (relying on the return values of it's own functions, as stored in external variables). But that's the design of the dispatcher. Unless you're going to write a new dispatcher, that's just part of the deal.
It's also worth pointing out that if you create a true circular dependency by calling multiple waitFors that deadlock against one another, the dispatcher will correctly throw an error saying as much;
Dispatcher.waitFor(...): Circular dependency detected while waiting for ID_#

Flux without data caching?

Almost all examples of flux involve data cache on the client side however I don't think I would be able to do this for a lot of my application.
In the system I am thinking about using React/Flux, a single user can have 100's of thousands of the main piece of data we store (and 1 record probably has at least 75 data properties). Caching this much data on the client side seems like a bad idea and probably makes things more complex.
If I were not using Flux, I would just have a ORM like system that can talk to a REST API in which case a request like userRepository.getById(123) would always hit the API regardless if I requested that data in the last page. My idea is to just have the store have these methods.
Does Flux consider it bad that if I were to make request for data, that it always hit the API and never pulls data from a local cache instance? Can I use Flux in a way were a majority of the data retrieval requests are always going to hit an API?
The closest you can sanely get to no caching is to reset any store state to null or [] when an action requesting new data comes in. If you do this you must emit a change event, or else you invite race conditions.
As an alternative to flux, you can simply use promises and a simple mixin with an api to modify state. For example, with bluebird:
var promiseStateMixin = {
thenSetState: function(updates, initialUpdates){
// promisify setState
var setState = this.setState.bind(this);
var setStateP = function(changes){
return new Promise(function(resolve){
setState(changes, resolve);
});
};
// if we have initial updates, apply them and ensure the state change happens
return Promise.resolve(initialUpdates ? setStateP(initialUpdates) : null)
// wait for our main updates to resolve
.then(Promise.params(updates))
// apply our unwrapped updates
.then(function(updates){
return setStateP(updates);
}).bind(this);
}
};
And in your components:
handleRefreshClick: function(){
this.thenSetState(
// users is Promise<User[]>
{users: Api.Users.getAll(), loading: false},
// we can't do our own setState due to unlikely race conditions
// instead we supply our own here, but don't worry, the
// getAll request is already running
// this argument is optional
{users: [], loading: true}
).catch(function(error){
// the rejection reason for our getUsers promise
// `this` is our component instance here
error.users
});
}
Of course this doesn't prevent you from using flux when/where it makes sense in your application. For example, react-router is used in many many react projects, and it uses flux internally. React and related libraries/patters are designed to only help where desired, and never control how you write each component.
I think the biggest advantage of using Flux in this situation is that the rest of your app doesn't have to care that data is never cached, or that you're using a specific ORM system. As far as your components are concerned, data lives in stores, and data can be changed via actions. Your actions or stores can choose to always go to the API for data or cache some parts locally, but you still win by encapsulating this magic.

how to cancel WCF service call?

I have a WCF function that is executing long time, so I call the function in UI with backgraundworker... I want to give a feature to cancel the execution, so I abort IComunicationObject, the problem is that Service execution is not stoping, Is there any way to stop Service execution in this case?
You may not need a BackgroundWorker. You can either make the operation IsOneWay, or implement the asynchronous pattern. To prevent threading issues, consider using the SynchronizationContext. Programming WCF Services does a great job at explaining these.
Make a CancelOperation() method which sets some static ManualResetEvent in your service. Check this event in your Operation method frequently. Or it can be CancelOperation(Guid operationId) if your service can process multiple operation calls concurrently.
One important thing to understand if you're using the Async calls is that there's still no way to cancel a request and prevent a response coming back from the service once it's started. It's up to your UI to be intelligent in handling responses to avoid race conditions. Fortunately there's a simple way of doing this.
This example is for searching orders - driven by a UI. Lets assume it may take a few seconds to return results and the user is running two searches back to back.
Therefore if your user runs two searches and the first search returns after the second - you need to make sure you don't display the results of the first search.
private int _searchRequestID = 0; // need one for each WCF method you call
// Call our service...
// The call is made using the overload to the Async method with 'UserToken'.
// When the call completes we check the ID matches to avoid a nasty
// race condition
_searchRequestID = _searchRequestID++;
client.SearchOrdersCompleted += (s, e) =>
{
if (_searchRequestID != (int)e.UserState))
{
return; // avoid nasty race condition
}
// ok to handle response ...
}
client.SearchOrdersAsync(searchMessage, _searchRequestID);