I'm completely stuck on an Apollo problem, for which I've opened a GitHub issue and had zero response on.
I'm calling an Apollo mutation, using optimisticResponse. The way it's supposed to work, as I understand it, is that update() gets called twice: first with the optimistic data, then again with the actual data coming in from the network.
But for some reason, my code is not working like this. I'm getting two update() calls, both with the optimistic data.
Here's a repo that demonstrates this behavior: https://github.com/ffxsam/apollo-update-bug
yarn && yarn dev
Open in browser, open console
Enter some text and hit enter
Repeat above
Notice the error in the console about duplicate keys. This is happening because the temporary ID "??" is not being replaced with the real UUID
(optional) You can open Vue DevTools if available and inspect the data to see it's incorrect
I was doing some digging and I think I found the source of the problem.
Unfortunately, I don't have a solution.
In short, the problem might be with a network link called OfflineLink that is used by aws-appsync.
Explanation
aws-appsync has an ApolloLink called OfflineLink that intervenes with the request function.
What happens is something like this:
you call $apollo.mutate(...)
ApolloClient.QueryManager initializes the mutation that triggers your update the first time with the optimistic response. That is happening inside ApolloClient data store, markMutationInit calls markMutationResult that calls your update.
The graphql operation executes and reaches the OfflineLink in the network chain.
OfflineLink creates a new observer and dispatches the mutation info as an action.
The next line of OfflineLink calls the observer's next function with the optimisticResponse as if it was the execution result!
This triggers your update the second time with the result which is actually the optimisticResponse.
OfflineLink calls the observer's complete which resolves your promise.
console.log('done!'...
Meanwhile, OfflineLink prevents the original mutation from even sending the request, and a new mutation is generated and sent with the options you've given it.
Related
Let's say I have a notes app. I want to enable the user to make changes while he is offline, save the changes optimistically in a Mobx store, and add a request to save the changes (on the server) to a queue.
Then when the internet connection is re-established I want to run the requests in the queue one by one so the data in the app syncs with data on the server.
Any suggestions would help.
I tried using react-native-job-queue but it doesn't seem to work.
I also considered react-native-queue but the library seems to be abandoned.
You could create a separate store (or an array in AsyncStorage) for pending operations, and add the operations to an array there when the network is disconnected. Tell your existing stores to look there for data, so you can render it optimistically. Then, when you detect a connection, run the updates in array order, and clear the array when done.
You could also use your existing stores, and add something like pending: true to values that haven't posted to your backend. However, you'll have less control over the order of operations, which sounds like it is important.
As it turns out I was in the wrong. The react-native-job-queue library does work, I just made a mistake by trying to pass a function reference (API call) to the Worker instead of just passing an object that contains the request URL and method and then just implement the Worker to make the API call based on those parameters.
I have two Vuex actions that both issue an axios request. I need to call one after the other, however, the second should only be called on the client side.
The first one, let's call it fetchContent is going to give me an object with details about the content that I need to fetch. The second one, fetchAd is going to fetch an ad, and it needs to know the id of the content object.
Due to various constraints, the ad should only be fetched on the client side, so just doing two awaits inside fetch will not.
I also thought about handling fetchContent inside fetch and then calling fetchAd inside mounted, but then I can't be sure by the time the page is mounted I actually have the response from fetchContent.
Another scenario I thought of is to just issue the fetchAd call from inside fetchContent, but there are several different variations of fetchContent and embedding fetchAd in every one of them would yield unnecessary complexity.
What's a clean, best-practice solution here?
Is possible to get which component call some action Vuex ?
I return a promise from vuex actions and take some decisions in component as set errors messages on respective fields but I would like to set on component.$validator.errors asap I receive http response (in action method)
Is that possible and a good approach ?
In Vuex when you make a call to a mutation or action you can send an object alongside your dispatch call, if you wanted to track the component which called an action you could also send that as part of the object. Better yet you could have all objects sent via Vuex extend a specific class if your using typescript. Though note that finding which component called an action or mutation is not native behavior for Vuex.
Consider the following:
try{
let out = await this.$store.dispatch('someActionHandler', {referingComponent: this.$options.name, someParam:[1,2,3]})
} catch (e){
// Lets deal with the problem
}
In here we are sending the name of the component as a parameter, so it can be checked inside our action handler or alternatively you could just pass this straight to a mutation, though I think the former is a more likely case if you plan to build logic into this.
As for if this is a good approach, the answer to that is fairly subjective, I personally don't see any problems with the above approach. Though I would think it was an anti pattern if the majority of components are never checked or the added data ends up becoming meaningless fluff passed alongside every call.
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_#
My store.sync() can return success:false, and if it does, I would like to use something similar to Ext's failure callback to react to the error appropriately, but I did not find a possibility to use any builtin ST functions for this. sync has neither callback nor success nor failure option available in ST.
What did I overlook?
PS: I did find a workaround for success callback at Why is there no sync callback in Sencha Touch? but I need failure callback.
store.sync() is not where you need to look. Take a look at the proxy. Most likely you are using an Ajax request and that in turn will deliver a detailed success and failure.
I am now calling Ext.data.Model.save() on all Ext.data.Model instances that are dirty. This won't batch everything neatly together, but in 90% of the cases, only one item is edited anyways. The best is that this allows to check for failure on each and every item, and not only on the whole batch.